Automatizando Pedidos de Apagamento

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

O Regulamento Geral de Proteção de Dados (Regulamento Geral sobre a Proteção de Dados) é um regulamento europeu sobre proteção de dados e privacidade. Ele concede aos indivíduos o direito de solicitar a exclusão de seus dados pessoais, conhecido como o direito de exclusão. Se você armazenar qualquer Informação Pessoalmente IDável (IIP) de seus usuários, como

Em vez de lidar com solicitações manualmente, você pode configurar um webhook e usar um bot dentro de uma aplicação de mensageria de terceiros para automatizar o processo. Como Armazenamento de dados é a maneira mais comum de armazenar dados PII, este tutorial fornece uma exemplificação sobre como criar um bot dentro do Guilded ou Discord que usa o Open Cloud API for Data Sto

Fluxo de Trabalho

Ao concluir este Tutorial, você deve estar habilitado a criar um programa personalizado que é executado localmente para automatizar o gerenciamento de solicitações de exclusão de direitos dos usuários. O fluxo de trabalho para este processo é como segue:

  1. O Roblox Support recebe um pedido de exclusão de um usuário.
  2. O webhook do Roblox é acionado, contendo o ID do Usuário e uma lista de IDs de Start Place para as experiências que eles se juntaram nopayload.
  3. Seu bot ouve essas notificações de webhook, verifica sua autenticidade e utiliza a Open Cloud API for Data Stores para excluir os dados armazenados em armazenamentos de dados.
  4. O bot responde à mensagem de webhook no Discord ou Guilded com o status de exclusão.

Configurando um Webhook com integração de terceiros

Antes de criar um bot, configure um servidor com integração de webhook na aplicação de mensageria de terceiros. Em seguida, use o servidor para configurar um webhook no Painel do Criador.

Configurando um Servidor

As seguintes etapas mostram como configurar o servidor usando Guilded ou Discord.

  1. Crie um novo servidor Guilded. Se você não estiver familiarizado com o processo, veja Suporte Guilded.
  2. Sob as configurações de Privacidade ', o servidor é definido como privado. O servidor cria automaticamente um canal privado # general como seu canal padrão.
  3. Crie uma integração de webhook com o novo servidor e dê-lhe um nome que você pode facilmente entender, como GDPR Hook. Se você estiver des familiarizado com o processo, consulte Suporte Guildado.
  4. Copie a URL do webhook e armazená-la em um localseguro. Somente permita que membros de equipe confiáveis acesso, pois a divulgação da URL pode permitir que atores maliciosos enviem mensagens falsas e possam potencialmente excluir seus dados do usuário.

Configurando um Webhook no Roblox

Depois de obter o URL do terceiro servidor, use-o para ajustar um webhook no Painel do Criador. certifique-se de executar as seguintes configurações:

  • Adicione o URL do servidor da Guilded ou Discord como o Webhook URL .
  • Inclua um segredo personalizado . Embora um segredo seja opcional para completar a configuração, você deve incluir um para impedir que os atores maliciosos imitem o Roblox e excluam seus dados. Para mais informações sobre o uso de um segredo, veja Verificando a segurança do webhook.
  • Selecione Pedido de Exclusão Direito embaixo de Gatilhos .

Você pode testar o webhook usando o botão Testar Resposta para ver se você recebe uma notificação no canal # geral do seu servidor do Roblox. Se você não receber a notificações, tente novamente ou verifique suas configurações do servidor para solucionar o erro.

Example notification on Guilded

Configurando um Bot

Depois de adicionar o webhook, use-o para configurar o bot com as seguintes etapas:

  1. Abra a lista Todos os servidores clicando em seu ícone ou use o atalho:

    • CtrlS em Windows.
    • S no Mac.
  2. Selecione seu servidor para receber notificações de direito de exclusão.

  3. Expanda a lista em Casa do Servidor e selecione Gerenciar Bots .

  4. O servidor cria automaticamente um canal privado # general como seu canal padrão.

  5. Clique no botão Criar um Bot e adicione um nome de bot. Redirecionamentos de guilda o leva à página de configuração de Bots.

  6. Selecione a seção API na página de configuração do bot.

  7. Sob a seção Tokens, clique no botão Gerar Tokens.

  8. Salve e armazene o token gerado em um localseguro.

Criando uma Chave da API de Nuvem Aberta

Para permitir que seu bot de terceiros acesse seus armazenamentos de dados para armazenar dados IIPde usuários, crie uma chave de API de Nuvem Aberta que possa acessar suas experiências e adicionar a permissão Remover Entrada dos armazenamentos de dados para a exclusão de dados. Se você usar armazenamentos de d

Obtendo IDs de Experiências e Locais

Para o bot localizar os dados PII solicitados pelos usuários para exclusão, obtenha os seguintes identificadores de todas as experiências que você intenciona usar o bot para:

  • O ID do Universo , o identificador exclusivo de sua experiência.
  • O ID do Local de Início ID , o identificador exclusivo do local de início de uma experiência.

Para obter esses identificadores, abra a página Criações no Painel do Criador. Em seguida, selecione uma experiência e copie o ID do Universo e 2>Start Place ID2>.

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

Adicionando Scripts

Depois de configurar o webhook, o bot e a chave da API para armazenamentos de dados, adicione-os aos scripts que implementam a lógica de automação do bot. O exemplo a seguir usa o Python 3:

  1. Instale bibliotecas Python usando os seguintes comandos:

    Instale as Bibliotecas

    pip3 install discord
    pip3 install guilded.py==1.8.0
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copie e salve os seguintes scripts correspondentes a diferentes partes da lógica do bot no mesmo diretório:

    bot_config.py

    DISCORD_BOT_TOKEN = ""
    GUILDED_BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Dicionário do ID do local de partida para
    # (ID do universo, lista de (nome dos armazenamentos de dados, alcance e chave de entrada)) para
    # Armazenamento de Dados Padrão
    # Os dados do usuário armazenados sob essas entradas serão excluídos
    STANDARD_DATA_STORE_ENTRIES = {
    # ID do Local de Início
    111111111: (
    # ID do Universo
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Dicionário do ID do local de partida para
    # (ID do universo, lista de (nome dos armazenamentos de dados, alcance e chave de entrada)) para
    # Armazenamento de Dados Ordenado
    # Os dados do usuário armazenados sob essas entradas serão excluídos
    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
    # Impede o ataque de replay dentro da janela 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 a assinatura
    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
    # Assinatura 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):
    # Parses recebem mensagem para ID do usuário e ID do jogo
    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 e valida a mensagem
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # Exclui dados padrão armazenados de usuários
    [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)}")
    # Exclui dados solicitados armazenados de usuários
    [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. No arquivo bot_config.py para a configuração principal do bot:

    1. Definir DISCORD_BOT_TOKEN ou GUILDED_BOT_TOKEN para o token gerado por seu bot.
    2. Defina OPEN_CLOUD_API_KEY como a chave de API que você criou.
    3. Definir ROBLOX_WEBHOOK_SECRET como o segredo que você configurou ao configurar o webhook no Painel do Criador.
    4. Em STANDARD_DATA_STORE_ENTRIES e ORDERED_DATA_STORE_ENTRIES dicionários para localizar o armazenamento de dados de cada registro para excluir:
      1. Adicione seus IDs de Local Inicial copiados como chaves.
      2. Adicione IDs de Universo como o primeiro elemento do valor da lista.
      3. Substitua o segundo elemento do tupleo com o nome, alcance, nome da chave de entrada e ID de usuário associado de seus armazenamentos de dados. Se você usar um diferente esquema de dados, modifique para ajustar o seu próprio esquema de dados.
  4. Execute o seguinte comando para executar o bot:

    Executar Bot Guilded

    python3 guilded_bot.py
  5. O bot então começa a ouvir e verificar os webhooks do Roblox para direito de exclusão de solicitações e chamadas o ponto de terminação da Nuvem Aberta para excluir o lojade dados correspondente.

Testando

Você pode criar e executar uma mensagem de teste para verificar que seu programa personalizado pode lidar com solicitações de exclusão com sucesso e excluir dados PII:

  1. Envie uma solicitação HTTP para o seu servidor webhook Guilded ou Discord com o seguinte corpo da solicitação:

    Exemplo de Pedido

    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. Se você tiver um webhook secreto:

    1. Gerar uma Roblox-Signature aplicando a encriptação HMAC-SHA256 à sua chave de webhook secreta.
    2. Defina a hora atual usando o UTC timestamp em segundos como Timestamp .
  3. Coloque juntos o description no seguinte 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 exemplo:

    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

Seu programa deve ser capaz de identificar que sua mensagem vem da fonte oficial do Roblox, pois você codificou a mensagem com seu segredo. Em seguida, deve excluir os dados PII associados à sua solicitar / pedir.

Corpo de Exemplo

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