일반 데이터 보호 규정(GDPR)은 유럽의 데이터 보호 및 개인 개인정보규정입니다. 개인에게는 개인 데이터의 삭제를 요청할 수 있는 권리(즉, 삭제 권리)가 부여됩니다. 사용자의 개인 데이터를 저장하면 사용자의 사
수동으로 요청을 처리하는 대신, 웹 후크를 설정하고 웹 메시징 애플리케이션 내에서 봇을 사용하여 프로세스를 자동화할 수 있습니다. As 데이터 저장소를 가장 일반적인 방법으로 저장하는 데 데이터 스토어를
워크플로
이 튜토리얼을 완료하면 사용자에게 제거 요청을 처리하는 데 사용되는 지역 실행 사용자 지정 프로그램을 만들 수 있어야 합니다. 이 프로세스의 작업 흐름은 다음과 같습니다.
- Roblox 지원은 사용자가 요청한 때에 삭제 요청을 받습니다.
- Roblox 웹 훅이 트리거되어 사용자 ID와 경험에 참여한 장소 ID 목록이 포함된 경험 별 목록이 전송됩니다.
- 이 웹 후크 알림을 듣고, 확인하고, 데이터 스토어에 저장된 PII 데이터를 삭제하기 위해 클라우드 API를 사용합니다.
- 봇은 Discord 또는 Guilded에서 삭제 상태로 웹 후크 메시지에 응답합니다.
제3자 통합 웹훅 구성
봇을 생성하기 전에 웹 훅 통합이 있는 제3자 메시징 응용 프로그램에서 서버를 설정하십시오. 그런 다음 서버를 사용하여 크리에이터 대시보드에서 웹 훅을 구성하십시오.
서버 설정
다음 단계에서는 Guilded 또는 Discord를 사용하여 서버를 설정하는 방법을 보여줍니다.
- 새로운 길드 서버를 생성합니다. 프로세스에 익숙하지 않은 경우 길드 지원 을 참조하십시오.
- 개인 정보 설정에 따라 서버를 비공개설정합니다. 서버는 기본 채널로 자동으로 개인 채널 # general을 생성합니다.
- 새로운 서버와 웹 후크 통합을 만들고 쉽게 이해할 수 있는 이름을 지정하십시오, 예를 들어 GDPR Hook. 프로세스에 익숙하지 않은 경우, 자산 보기를 참조하십시오.
- 웹 후크 URL을 복사하고 안전한 플레이스저장하십시오. 웹 후크 URL을 유출하면 신뢰할 수 없는 팀 멤버가 액세스할 수 있으므로 사용자 데이터를 손상시킬 수 있습니다.
Roblox에서 웹훅 구성
제3자 서버 URL을 받은 후 크리에이터 대시보드에서 웹훅 구성을 구성하세요 make sure you perform the following settings:
- Guilded 또는 Discord 서버 URL을 Webhook URL 로 추가합니다.
- 사용자 지정 된 비밀 Secret 를 포함하십시오. 구성을 완료하는 것이 선택적이지만, 사기꾼이 Roblox을 사기 및 데이터를 삭제하는 것을 방지하기 위해 한 개를 포함해야 합니다. 사이버 보안 사용에 대한 자세한 내용은 Roblox 사이버 보안 검색를 참조하십시오.
- Select Right to Erasure Request under Triggers .
테스트 응답 버튼을 사용하여 서버의 # general 채널에서 Roblox로부터 알림을 받는지 여부를 테스트하여 알림을 받지 못하는 경우 서버 설정을 다시 확인하거나 오류를 해결하십시오.
봇 구성
웹 후크를 추가한 후 다음 단계를 사용하여 봇을 구성하세요.
아이콘을 클릭하거나 바로가기를 사용하여 모든 서버 목록을 열거하십시오.
- CtrlS 에서 Windows에서.
- ⌘S 맥에서.
오른쪽 사서함 알림을 받으려는 서버를 선택하십시오.
서버 홈 아래에 있는 목록을 확장하고 봇 관리 를 선택합니다.
서버는 기본 채널로 자동으로 비공개 채널 # general을 생성합니다.
봇 생성 버튼을 클릭하고 봇 이름을 입력하십시오. 길드는 봇 구성 페이지로 이동합니다.
봇 구성 페이지에서 API 섹션을 선택합니다.
Under the 토큰 section, click the 토큰 생성 button.
생성된 토큰을 안전한 플레이스저장하십시오.
Open Cloud API 키 생성
사용자의 개인 식별 정보데이터를 저장하기 위해 제3자 봇이 데이터 저장소에 액세스할 수 있도록 하려면, Open Cloud API 키를 생성 하여 경험에 액세스하고 데이터 저장소의 데이터 삭제 권한을 추가할 수 있습니다.
경험과 장소의 식별자 얻기
사용자가 삭제를 요청한 PII 데이터를 찾을 수 있도록 다음 식별자를 모든 사용자가 사용하려는 모든 경험에 대해 얻으십시오.
- 경험의 고유 식별자인 유니버스 ID입니다.
- 경험의 시작 장소의 고유 식별자인 시작 장소 ID 입니다.
이 식별자를 얻으려면 크리에이터 대시보드의 생성 페이지를 엽니다. 그런 다음 경험을 선택하고 우주 ID 및 2>시작 장소 ID2>를 복사합니다.
스크립트 추가
데이터 저장소에 대한 웹 호크, 봇, API 키를 설정한 후 스크립트에 봇의 자동 로직을 구현하는 코드를 추가합니다. 다음 예시에서는 Python 3를 사용합니다.
다음 명령을 사용하여 파이톤 라이브러리를 설치하세요:
라이브러리 설치pip3 install discordpip3 install guilded.py==1.8.0pip3 install requestspip3 install urllib3==1.26.6다른 봇 로직 부품에 해당하는 스크립트를 복사하고 동일한 디렉터리에 저장하십시오.
bot_config.pyDISCORD_BOT_TOKEN = ""GUILDED_BOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# 시작 장소 ID의 사전# (宇宙 ID, 데이터 저장소 이름, 범위 및 키 입력 키)) for# 일반적인 데이터 저장소# 이 항목 아래에 저장된 사용자 데이터는 삭제됩니다.STANDARD_DATA_STORE_ENTRIES = {# 시작 장소 ID111111111: (# 유니버스 ID222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# 시작 장소 ID의 사전# (宇宙 ID, 데이터 저장소 이름, 범위 및 키 입력 키)) for# 주문형 데이터 저장소# 이 항목 아래에 저장된 사용자 데이터는 삭제됩니다.ORDERED_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, failures메시지_액터.pyimport 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# 300초 창 내의 리플레이 공격 방지request_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# 서명 유효성 검사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# 유효한 서명return 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):# 사용자 ID 및 게임 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_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):# 메시지 구문을 분석하고 유효합니다.user_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# 일반적인 데이터 저장소 사용자 데이터를 삭제합니다.[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)}")# 주문된 데이터 저장소의 사용자 데이터를 삭제합니다.Deletes ordered data stores user data[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()봇의 메인 구성에 대한 bot_config.py 파일:
- 봇에 생성된 토큰에 DISCORD_BOT_TOKEN 또는 GUILDED_BOT_TOKEN을 설정합니다.
- API 키로 OPEN_CLOUD_API_KEY를 설정합니다.
- 크리에이터 대시보드에서 웹 후크를 구성할 때 비밀로 ROBLOX_WEBHOOK_SECRET를 설정하세요.
- 각 레코드의 데이터 저장소를 삭제하려면 STANDARD_DATA_STORE_ENTRIES 및 ORDERED_DATA_STORE_ENTRIES 사전에서 찾아야 합니다.
- 복사한 시작 장소 ID를 열에 추가합니다.
- 첫 번째 요소로 유니버스 ID를 추가합니다.
- 테이블의 두 번째 요소를 이름, 범위, 키 이름 및 데이터 저장소의 연관된 사용자 ID로 대체합니다. 다른 데이터 스키마를 사용하는 경우 해당 데이터 스키마에 대해 수정합니다.
다음 명령을 실행하여 봇을 실행합니다.
길드 봇 실행python3 guilded_bot.py이제 봇이 로블록스 웹 호크를 듣고 제거 요청 및 호출에 대해 Roblox 웹 호크를 확인하고 해당 데이터 상점삭제하기 위해 Open Cloud 엔드포인트를 호출합니다.
테스트
사용자 정의 프로그램이 오른쪽 삭제 요청을 올바르게 처리하고 PII 데이터를 삭제하도록 테스트 메시지를 만들고 실행할 수 있습니다.You can create and run a test message to verify that your custom program can properly handle right to erasure requests and delete PII data:
다음 요청 신체가진 HTTP POST 요청을 길드 또는 Discord 웹훅 서버에 보내십시오.
예시 요청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}"}}]}'웹 후크 비밀이 있으면:
- 웹 훅 비밀 키에 HMAC-SHA256 인코딩을 적용하여 Roblox-Signature 생성.
- 현재 시간을 UTC 타임스탬프를 사용하여 Timestamp 로 설정합니다.
다음 형식으로 description을 모으세요:
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}`.예를 들어:
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
프로그램에서 비밀로 메시지를 암호화했기 때문에 Roblox 본사에서 메시지를 식별할 수 있어야 합니다. 그런 다음 요청과 관련된 PII 데이터를 삭제해야 합니다.
예시 바디
{
"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"
}
}
]
}