Automatyzacja prawa do usuwania wniosków

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

Ogólne rozporządzenie o ochronie danych (dane osobowe) jest europejskim rozporządzeniem w sprawie ochrony danych i prywatność, poufność. Daje osobom prawo do żądania usunięcia ich osobistych danych, znanych jako prawo do usunięcia. Jeśli przechowujesz jakiekolwiek osobiste dane identyfikujące Zamiast wysyłać wnioski ręcznie, możesz ustawić webhook i użyć botu w aplikacji czatu stron trzecich, aby automatyzować ten proces. Gdy przechowywanie danych jest najczęstszym sposobem na przechowywanie danych PII, ten samouczek pokazuje przykład na to, jak utworzyć bot w Guilded

Przepływ

Po ukończeniu tego samouczka powinieneś być w stanie stworzyć lokalnie uruchomiony program personalizowany, który automatyzuje zarządzanie wnioskami o usunięcie od użytkowników. Workflow dla tego procesu jest następujący:

  1. Roblox Support otrzymuje prawo do wniosku o usunięcie od użytkownika.
  2. Roblox webhook jest uruchomiony, zawierający ID użytkownika i listę ID miejsca startowego dla doświadczeń, które dołączyły do ładowania.
  3. Twój bot słucha tych powiadomień o webhookach, weryfikuje ich autentyczność i wykorzystuje Open Cloud API for Data Stores do usuwania przechowywanych danych w magazynach danych.
  4. Bot odpowiada na wiadomość webhook w Discord lub Guilded z statusem usunięcia.

Konfiguracja w Webhook z integracją stron trzecich

Zanim stworzysz Bota, skonfiguruj serwer z wbudowaną integracją wiadomości na aplikacji wiadomości trzeciej strony. Następnie użyj serwera, aby skonfigurować wiadomość w Bocie Creatora.

Uruchomienie serwera

Poniższe kroki pokazują, jak skonfigurować serwer używając Guilded lub Discord.

  1. Utwórz nowy serwer Guilded. Jeśli nie jesteś znajomy z procesem, zobacz Guilded Support.
  2. Pod ustawieniami prywatności, ustaw serwer na prywatne. Serwer automatycznie tworzy prywatny kanał #general jako twoją domyślną kamerę.
  3. Utwórz integrację webhook z nowym serwerem i nadaj mu imię, które możesz łatwo zrozumieć, takie jak GDPR Hook. Jeśli nie jesteś znajomy z procesem, zobacz Guilded Support.
  4. Kopiuj URL webhook i przechowywaj go w bezpiecznym miejsce. Umożliwь tylko zaufanym członkom zespołu dostępu do niego, ponieważ ujawnienie URL może umożliwić fałszywym aktorom wysyłanie fałszywych wiadomości i potencjalnie usunięcie twoich danych użytkownika.

Konfiguracja wtyczki w Roblox

Po uzyskaniu trzeciego adresu URL serwera użyj go, aby skonfigurować webhook na Dashboardu Twórcy. upewnij się, że wykonujesz następujące ustawienia:

  • Dodaj URL serwera Guilded lub Discord jako Webhook URL .
  • Włącz niestandardowy tajny . Chociaż tajny jest opcjonalny do ukończenia konfiguracji, powinieneś go włączyć, aby zapobiec złym aktom na impersonifikacji Roblox i usunięciu Twoich danych. Dla więcej informacji o użyciu tajnego, zobacz Zabezpieczanie bezpieczeństwa Webhook.
  • Wybierz Prawo do żądania usunięcia pod Spowodami .

Możesz testować webhook używając przycisku Testuj odpowiedź, aby zobaczyć, czy otrzymujesz powiadomienie w kanałach # general Roblox. Jeśli nie otrzymujesz powiadomienia, spróbuj ponownie lub sprawdź ustawienia serwera, aby rozwiązać problem.

Example notification on Guilded

Konfiguracja Bota

Po dodaniu webhook'a użyj go, aby skonfigurować Bota za pomocą następujących kroków:

  1. Otwórz listę Wszystkie serwery poprzez kliknięcie jego ikony lub użyj skrótu:

    • CtrlS na Windows.
    • S na Mac.
  2. Wybierz swój serwer, aby otrzymywać wiadomości o prawie do usunięcia.

  3. Rozwój listy pod Strona główna serwera i wybierz Zarządzaj Botami.

  4. Serwer automatycznie tworzy prywatny kanał #general jako twój domyślny kanał.

  5. Kliknij przycisk Utwórz bota i dodaj imię bota. Gilded przekieruje cię na stronę konfiguracji bota.

  6. Wybierz sekcję API na stronie konfiguracji botów.

  7. W sekcji Tokeny, kliknij przycisk Generuj Token.

  8. Zapisz i przechowuj generowany token w bezpiecznym miejsce.

Tworzenie klucza API Open Cloud

Aby umożliwić swojemu trzeciemu botowi dostęp do twoich magazynów danych, aby przechowywać dane osobowe użytkowników, utwórz klucz API, który może uzyskać dostęp do twoich doświadczeń i dodać permisję Usuń wpis dla magazynów danych dla celu usunięcia

Zdobywanie identyfikatorów doświadczeń i miejsc

Aby Bot lokalizował dane PII wymagane przez użytkowników do usunięcia, uzyskaj następujące identyfikatory wszystkich doświadczeń, które chcesz użyć dla Bot:

  • Uniwersalny identyfikator Twojego doświadczenia , unikalny identyfikator Twojego doświadczenia.
  • Start Place ID, unikalny identyfikator początkowego miejsca doświadczenia.

Aby uzyskać te identyfikatory, otwórz stronę Kreacje na Panelu twórcy. Następnie wybierz doświadczenie i skopiuj Identyfikator uniwersum i 2>Początek miejsca2>.

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

Dodawanie Skryptów

Po ukończeniu ustawień webhook, bot i klucz API dla magazynów danych dodaj je do skryptów, które implementują logikę automatyzacji bot'a. Poniższy przykład używa Python 3:

  1. Zainstaluj biblioteki Python za pomocą następujących komend:

    Zainstaluj biblioteki

    pip3 install discord
    pip3 install guilded.py==1.8.0
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Kopiuj i zapisz następujące skrypty odpowiadające poszczególnym częściom logiki botów w tym samym katalogu:

    bot_konfiguruj.py

    DISCORD_BOT_TOKEN = ""
    GUILDED_BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Słownik ID miejsca startowego do
    # (identyfikator wszechświata, lista (nazwa magazynów danych, zakres i klucz wpisu)) dla
    # Standardowe magazyny danych
    # Dane użytkownika przechowywane pod tymi wpisami zostaną usunięte
    STANDARD_DATA_STORE_ENTRIES = {
    # ID Miejsce Inicjacyjne
    111111111: (
    # ID wszechświata
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Słownik ID miejsca startowego do
    # (identyfikator wszechświata, lista (nazwa magazynów danych, zakres i klucz wpisu)) dla
    # Przechowywanie danych w porządku
    # Dane użytkownika przechowywane pod tymi wpisami zostaną usunięte
    ORDERED_DATA_STORE_ENTRIES = {
    111111111: (
    222222222,
    [
    ("OrderedDataStore1", "Scope2", "Key4_{user_id}")
    ]
    )
    }
    api_przechowywania_danych.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
    # Zapobiega atakowi odtwarzalności w ciągu 300 sekund
    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
    # Zweryfikowuje podpis
    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
    # Prawidłowa podpisywanie
    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):
    # Otrzymuje wiadomość dla użytkownika ID i gry ID
    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):
    # P Parses i waliduje wiadomość
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # Usuwa standardowe magazyny danych użytkowników
    [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)}")
    # Usuwa przechowywane dane użytkownika
    [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. Na pliku bot_config.py dla głównej konfiguracji botu:

    1. Ustaw DISCORD_BOT_TOKEN lub GUILDED_BOT_TOKEN do generowanego przez twojego bota tokenu.
    2. Ustaw OPEN_CLOUD_API_KEY jako klucz API, który stworzyłeś.
    3. Ustaw ROBLOX_WEBHOOK_SECRET jako sekret, który ustawiłeś podczas konfiguracji webhook na Dashboardze Twórcy.
    4. W STANDARD_DATA_STORE_ENTRIES i ORDERED_DATA_STORE_ENTRIES słownikach do lokalizacji magazynu danych każdej rekordu do usuwać:
      1. Dodaj swoje skopiowane ID Start Place jako klucze.
      2. Dodaj ID wszechświata jako pierwszy element wartości zapisu.
      3. Zastąp drugi element tuplu imieniem, zakresem, nazwą klucza wpisu i związanym ID użytkownika twoich magazynów danych. Jeśli używasz innego schematu danych, modyfikuj zgodnie z własnym schematem danych.
  4. Wykonaj następujące polecenie, aby uruchomić Bota:

    Zacznij bota z guildą

    python3 guilded_bot.py
  5. Bot wtedy zaczyna słuchać i weryfikować wszystkie wiadomości w Roblox Webhook, aby mieć prawo do usuwania wniosków o usunięcie i wezwywania Open Cloud Endpoint, aby usunąć odpowiednią przestrzeń dyskową.

Testowanie

Możesz utworzyć i z実行ować testową wiadomość, aby zweryfikować, że twój niestandardowy program może prawidłowo zarządzać wnioskami o usunięcie i usuwania danych PII:

  1. Wyślij prośbę HTTP POST na swojego guilded lub Discord webhook server z następującym ciałoprośby:

    Przykładowy zapis

    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. Jeśli masz sekret wtyczki witryny:

    1. Generuj Roblox-Signature poprzez zastosowanie szyfrowania HMAC-SHA256 do twojego klucza sekretnego wtyczki.
    2. Ustaw obecny czas używając czasu UTC w sekundach jako Timestamp .
  3. Połącz description w następującej formie:

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

    Na przykład:

    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

Twój program powinien być w stanie zidentyfikować, że wiadomość pochodzi od oficjalnego źródła Roblox, ponieważ zaszyfrowałeś wiadomość swoim sekretem. Następnie powinien usunąć dane PII związane z twoim prośba.

Przykładowy organizm

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