Automatisation des demandes d'effacement

*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 (RGPD) 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, connue sous le nom de droit à l'effacement. Si vous stockez n'importe quelle donnée personnalisable de vos utilisateurs, telle que leurs ID

Au lieu de gérer manuellement les demandes, vous pouvez configurer un webhook et utiliser un bot dans une application de messagerie tiers pour automatiser le processus. Comme les données des magasins de données sont le moyen le plus courant de 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 API ouverte pour les don

Flux de travail

Après avoir terminé ce tutoriel, vous devriez être en mesure de créer un programme personnalisé s'exécutant localement qui automatise le traitement des demandes d'annulation des droits des utilisateurs. Le workflow pour ce processus est comme suivant :

  1. Le support Roblox reçoit une demande d'effacement d'un utilisateur.
  2. Le webhook Roblox est déclenché, contenant l'ID de l'utilisateur et une liste d'identifiants de lieu de départ pour les expériences qu'ils ont rejointes dans le chargement.
  3. Votre bot écoute ces notifications de webhook, vérifie leur authenticité et utilise la API ouverte pour les données des 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 dans Discord ou Guilded avec le statut de suppression.

Configurer un Webhook avec l'intégration tiers

Avant de créer un bot, configurez un serveur avec l'intégration webhook sur l'application de messagerie tiers. Puis 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éer un nouveau serveur Guilded. Si vous n'êtes pas familier avec le processus, voir Support Guilded.
  2. Sous les paramètres de confidentialité », le serveur est configuré pour être privé. Le serveur crée automatiquement un chat (chat privé)privé # général par défaut.
  3. Créez une intégration 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 Support de guilde.
  4. Copiez l'URL du webhook et stockez-la dans un emplacementsûr. Ne permettez que aux membres de l'équipe de confiance d'y accéder, car le piratage de l'URL peut permettre aux acteurs malveillants d'envoyer des messages faux et potentiellement de supprimer vos données utilisateur.

Configurer un webhook sur Roblox

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

  • Ajoutez l'URL du serveur Guilded ou Discord comme Webhook URL .
  • Incluez un secret personnalisé . Bien qu'un secret soit facultatif pour terminer la configuration, vous devriez l'inclure pour empêcher les mauvais acteurs 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 Demande d'effacement droit sous Trigger .

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

Example notification on Guilded

Configurer un Robot

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

  1. Ouvrez la liste Tous les serveurs en cliquant sur son icône ou en utilisant la raccourci :

    • CtrlS sur Windows.
    • S sur Mac.
  2. Sélectionnez votre serveur pour recevoir des notifications d'effacement à droite.

  3. Expand the list under Accueil du serveur and select Gérer les Robots .

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

  5. Cliquez sur le bouton Créer un bot et ajoutez un nom de bot. Les redirections de guilde vous redirigent vers la page de configuration du bot.

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

  7. Sous la section Jetons, cliquez sur le bouton Générer un jeton.

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

Créer une Clé APIOpen Cloud

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é API Open Cloud qui peut accéder à vos expériences et ajouter la permission Supprimer entrée des magasins de données pour la suppression des données. Si vous utilisez des magasins de données commandés pour stocker les données IPI,

Obtention d'identifiants d'expériences et de lieux

Pour que le bot localise les données PII demandées par les utilisateurs à supprimer, obtenez les identifiants suivants de toutes les expériences que vous souhaitez utiliser le bot pour :

  • Le identifiant de l'univers , l'identifiant unique de votre expérience.
  • Le lieu de départ ID , l'identifiant unique du lieu de départ d'une expérience.

Pour obtenir ces identifiants, ouvrez la page Créations sur Tableau de bord du créateur . Ensuite, sélectionnez une expérience et copiez le ID Universe et 1> Start Place ID1>.

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

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 discord
    pip3 install guilded.py==1.8.0
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copiez et enregistrez les scripts suivants qui correspondent à différentes parties de la logique du bot dans le même dossier :

    bot_config.py

    DISCORD_BOT_TOKEN = ""
    GUILDED_BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Dictionnaire 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
    # Stockages de données standard
    # Les données de l'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}")
    ]
    )
    }
    # Dictionnaire 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
    # Stockages de données commandés
    # Les données de l'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 la tentative de rejouer 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
    # Valide 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):
    # Démarre la réception du 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):
    # Décode 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 magasins de données standard les données des utilisateurs
    [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 magasins de données commandés
    [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. Sur le fichier bot_config.py pour la configuration principale du bot :

    1. Définir DISCORD_BOT_TOKEN ou GUILDED_BOT_TOKEN sur le jeton généré par votre bot.
    2. Définir OPEN_CLOUD_API_KEY comme clé API que vous avez créée.
    3. Définir ROBLOX_WEBHOOK_SECRET comme le secret que vous avez défini lors de la configuration du webhook sur la dashboard du créateur.
    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 Universe en tant que premier élément de la valeur Universe.
      3. Remplacez le deuxième élément de la tableau par le nom, le champ de champ d'entrée, la clé de nom de l'entrée et l'ID de l'utilisateur associé de vos magasins de données. Si vous utilisez un autre schéma de données, modifiez pour correspondre à votre propre schéma de données de manière correspondante.
  4. Exécutez la commande suivante pour exécuter le bot :

    Exécuter le Robot Guilded

    python3 guilded_bot.py
  5. Le bot commence alors à écouter et à vérifier les webhook Roblox pour le droit d'effacer les demandes et appelle l'Open Cloud pour supprimer la correspondante du boutiquede données.

Test

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

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

    Demande d'exemple

    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 de webhook :

    1. Générez une Roblox-Signature en appliquant l'encodage HMAC-SHA256 à votre clé webhook secret.
    2. Définir l'heure actuelle en utilisant le tampon de l'UTC dans secondes comme Timestamp .
  3. Mettez ensemble 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 est de la source Roblox officielle 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": "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/dash/ressources/webhook/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}