Il Regolamento generale sulla protezione dei dati (Informazioni di identificazione personale (PII)) è un regolamento europeo sulla protezione dei dati e sulla Privacy. Garanzie agli individui il diritto di richiedere la cancellazione dei loro dati personali, noti come il diritto all'oblio. Se memorizzi qualsiasi Informazione personalmente identificabile dei tuoi utenti, come i loro ID utente, devi conformart
Invece di gestire le richieste manualmente, puoi impostare un webhook e utilizzare un bot all'interno di un'applicazione di messaggi a terze parti per automatizzare il processo. Poiché i archivi di dati sono il modo più comune per archiviare i dati PII, questo tutorial fornisce un esempio su come creare un bot all'interno di Guilded o Discord che utilizza il
Flusso di lavoro
Al completamento di questo Tutoriale, dovresti essere in grado di creare un programma personalizzato in esecuzione localmente che automatizza il trattamento delle richieste di cancellazione dei utenti. Il flusso di lavoro per questo processo è come segue:
- Roblox Support riceve una richiesta di cancellazione di un utente.
- Il webhook Roblox viene attivato, contenendo l'ID utente e una lista di ID Start Place per le esperienze che hanno partecipato nel payload.
- Il tuo bot ascolta queste notifiche webhook, verifica la loro autenticità e utilizza il Open Cloud API for data stores per eliminare i dati PII memorizzati nei datastore.
- Il bot risponde al messaggio webhook in Discord o Guilded con lo Statodi eliminazione.
Configurazione di un Webhook con l'integrazione di terze parti
Prima di creare un bot, imposta un server con l'integrazione webhook sull'applicazione di messaggi di terze parti. Quindi usa il server per configurare un webhook sul Dashboard Creatore.
Configurazione di un server
I seguenti passaggi mostrano come configurare il server usando Guilded o Discord.
- Crea un nuovo ServerGuilded. Se non sei a conoscenza del processo, vedi Supporto Guilded.
- Sotto le impostazioni Privacy , imposta il server su Server privato. Il server crea automaticamente un canale privato #generico come canale predefinito.
- Crea un'integrazione con il webhook con il nuovo server e dargli un nome che puoi facilmente capire, come GDPR Hook. Se non sei a conoscenza del processo, vedi Supporto Guilded.
- Copia l'URL del webhook e memorizzalo in un Postosicuro. Consenti solo ai membri del team autorizzati ad accedervi, poiché il rilascio dell'URL può consentire agli attori non autorizzati di inviare messaggi falsi e potenzialmente eliminare i tuoi dati utente.
Configurazione di un Webhook su Roblox
Dopo aver ottenuto l'URL del server di terze parti, usalo per configurare un webhook sulla dashboard del creatore. assicurati di eseguire le seguenti impostazioni:
- Aggiungi l'URL del server della gilda o Discord come URL del Webhook .
- Includi un segreto personalizzato . Anche se un segreto è opzionale per completare la configurazione, dovresti includerlo per impedire agli attori malvagi di impersonare Roblox e eliminare i tuoi dati. Per ulteriori informazioni sull'uso di un segreto, vedi Verifica della sicurezza dell'Webhook.
- Seleziona Richiesta di cancellazione della destra sotto Trigger .
Puoi testare il webhook utilizzando il pulsante Risposta di prova per vedere se ricevi una notifica nel tuo canale # generale da Roblox. Se non ricevi la Notifiche, prova di nuovo o controlla le impostazioni del tuo Serverper risolvere il problema.
Configurazione di un Bot
Dopo aver aggiunto il webhook, usalo per configurare il bot con i seguenti passaggi:
Apri la lista Tutti i server facendo clic sull'icona o usando la Scorciatoia:
- CtrlS su Windows.
- ⌘S su Mac.
Seleziona il tuo server per ricevere notifiche di cancellazione con il diritto di erasure.
Espandi la lista sotto Server home e seleziona Gestisci bot .
Il server crea automaticamente un canale privato # generale come canale predefinito.
Fai clic sul pulsante Crea un bot e aggiungi un nome del bot. I guilded ti rimanda alla pagina di configurazione del bot.
Seleziona la sezione API sulla pagina di configurazione del bot.
Nella sezione Gettoni, fai clic sul pulsante Genera Token.
Salva e memorizza il token generato in un Postosicuro.
Creazione di una Chiave dell'APIOpen Cloud
Per consentire al tuo bot di terze parti di accedere ai tuoi magazzini di dati per archiviare i dati PII degli utenti, crea una chiave API Open Cloud che può accedere alle tue esperienze e aggiungere il permesso Elimina modifica dei magazzini di dati per la cancellazione dei dati. Se usi magazzini di dati ordinati per archiviare i dati P
Ottenere ID delle esperienze e dei luoghi
Per il bot per localizzare i dati PII richiesti dagli utenti per la cancellazione, ottieni i seguenti identificatori di tutte le esperienze che intenzione di utilizzare il bot per:
- Il ID Universe , l'identificatore unico della tua esperienza.
- Il ID luogo di partenza ID , l'identificatore unico del luogo di partenza di un'esperienza.
Per ottenere questi identificatori, apri la pagina Creazioni su Dashboard del Creatore . Quindi seleziona un'esperienza e copia il ID Universe e 2>Start Place ID2> .
Aggiungere script
Dopo aver finito di impostare il webhook, il bot e la chiave API per i dati, aggiungili agli script che implementano la logica di automazione del bot. Il seguente esempio usa Python 3:
Installa librerie Python usando i seguenti comandi:
Installare le libreriepip3 install discordpip3 install guilded.py==1.8.0pip3 install requestspip3 install urllib3==1.26.6Copia e salva gli script seguenti corrispondenti a diverse parti della logica del bot nella stessa cartella:
bot_config.pyDISCORD_BOT_TOKEN = ""GUILDED_BOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Dizionario dell'ID del luogo di partenza per# (ID universo, elenco di (nome dei magazzini di dati, scala e chiave di accesso)) per# Store di dati standard# I dati dell'utente memorizzati sotto queste entrade verranno eliminatiSTANDARD_DATA_STORE_ENTRIES = {# ID luogo di partenza111111111: (# ID dell'Universo222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Dizionario dell'ID del luogo di partenza per# (ID universo, elenco di (nome dei magazzini di dati, scala e chiave di accesso)) per# Negozi di dati ordinati# I dati dell'utente memorizzati sotto queste entrade verranno eliminatiORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Calls Data Stores Open Cloud API to delete all entries for a user_id configured inSTANDARD_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:continueuniverse_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 inORDERED_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:continueuniverse_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, failuresmessage_ parsleyimport timeimport hmacimport hashlibimport reimport base64import bot_config"""Parses received message for Roblox signature and timestamp, the footer is only set if youconfigured webhook secret"""def parse_footer(message):if not message.embeds[0].footer or \not message.embeds[0].footer.text:return "", 0footer_match = re.match(r"Roblox-Signature: (.*), Timestamp: (.*)",message.embeds[0].footer.text)if not footer_match:return "", 0else: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# Impedisce l'attacco di riproduzione entro la finestra di 300 secondirequest_timestamp_ms = timestamp * 1000window_time_ms = 300 * 1000oldest_timestamp_allowed = round(time.time() * 1000) - window_time_msif request_timestamp_ms < oldest_timestamp_allowed:return False# Firma con validità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 validareturn True"""Parses a received webhook messaged on Discord or Guilded. Extracts user ID, prevents replay attackbased on timestamp received, and verifies Roblox signature with configured secret to check forvalidity."""def parse_message(message):# Parifica il messaggio per l'ID dell'utente e l'ID del giocoif 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_idselse:return "", []guilded_bot.pyimport guildedimport jsonimport bot_configimport data_stores_apiimport message_parserdef run():client = guilded.Client()@client.eventasync def on_ready():print(f"{client.user} is listening to Right to Erasure messages")"""Handler for webhook messages from Roblox"""@client.eventasync def on_message(message):# Parsa e convalida il Messaggiouser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Elimina i dati di archiviazione standard memorizza i dati utente[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 i dati memorizzati utente[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()Nel file bot_config.py per la configurazione principale del bot:
- Imposta DISCORD_BOT_TOKEN o GUILDED_BOT_TOKEN sul token generato dal tuo bot.
- Imposta OPEN_CLOUD_API_KEY come la chiave API che hai creato.
- Imposta ROBLOX_WEBHOOK_SECRET come segreto che hai impostato quando hai configurato il webhook sulla dashboard del creatore.
- In STANDARD_DATA_STORE_ENTRIES e ORDERED_DATA_STORE_ENTRIES dizionari per localizzare il magazzino di dati di ciascun record da Eliminare:
- Aggiungi i tuoi ID di luogo di partenza copiati come chiavi.
- Aggiungi ID Universe come primo elemento del valore tupla.
- Rimpiazza il secondo elemento della lista con il nome, la scala, l'identificatore chiave e l'ID utente associato dei tuoi dati di archiviazione. Se usi un altro schema di dati, modifica per corrispondere al tuo stesso schema di dati in modo da poter essere utilizzato.
Esegui il seguente comando per eseguire il bot:
Esegui bot gildatopython3 guilded_bot.pyIl bot quindi inizia a ascoltare e verificare le richieste e gli errori dei webhook Roblox per il diritto di cancellazione e chiama l'endpoint Open Cloud per eliminare il relativo Negoziodi dati.
Testare
Puoi creare e eseguire un messaggio di test per verificare che il tuo programma personalizzato possa gestire correttamente le richieste di cancellazione e eliminare i dati PII:
Invia una richiesta HTTP POST ai tuoi server di Guilded o Discord con il seguente corpo della richiesta:
Richesta di esempiocurl -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}"}}]}'Se hai un webhook segreto:
- Genera una Roblox-Signature applicando l'encodazione HMAC-SHA256 alla tua chiave webhook segreta.
- Imposta l'ora corrente utilizzando il timestamp UTC in secondi come Timestamp .
Metti insieme il description nel seguente 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}`.Ad esempio:
Example Description Field1683927229. You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955
Il tuo programma dovrebbe essere in grado di identificare che il tuo messaggio è dalla fonte ufficiale Roblox poiché hai incodificato il messaggio con il tuo segreto. Quindi dovrebbe eliminare i dati PII associati alla tua Richiesta.
Corpo di esempio
{
"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://creare.roblox.com/ashboard/asset/webhook/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}