From eb7ff4ec6a230055b9adf6fcb47957010862d473 Mon Sep 17 00:00:00 2001 From: HeuJung Date: Tue, 1 Sep 2026 08:20:36 +0900 Subject: [PATCH] =?UTF-8?q?fix(gdpr,hooks):=20=EB=8F=99=EC=9D=98=20?= =?UTF-8?q?=EC=B6=9C=EC=B2=98=20=EC=96=B4=ED=9C=98=20=EC=A0=95=EC=A0=95?= =?UTF-8?q?=EA=B3=BC=20=ED=99=95=EC=9E=A5=20=EB=AC=B8=EC=84=9C=20=EA=B0=95?= =?UTF-8?q?=EC=A0=9C=EC=9D=98=20Codex=20=ED=8A=B8=EB=A6=AC=20=EC=9D=B4?= =?UTF-8?q?=EC=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 쿠키 동의 저장 요청이 서버만 기록하는 출처(가입 시 동의)를 지정할 수 있었다. 주석은 지정 불가라고 적혀 있었고 코드가 그것을 잃은 쪽이었다 — 그 값을 쓰는 곳은 회원가입 리스너뿐이고, 받는 경로는 비인증 방문자도 도달한다. 형제 값 둘이 이미 같은 이유로 막혀 있어 대칭을 회복한다. 확장 문서 동반 강제가 두 도구 트리 중 한쪽에만 있었다. 다른 쪽으로 작업하면 문서를 빠뜨려도 아무 경고가 없고, 그 결과는 이상 0건과 구분되지 않는다. 룰· coverage·편집 시점 안내·세션 종료 검사·미채움 기준선을 이식하고, 두 사본이 같은 입력에 같은 판정을 내는지를 픽스처가 한 곳에서 단언하게 했다. --- plugins/_bundled/sirsoft-gdpr/CHANGELOG.md | 1 + plugins/_bundled/sirsoft-gdpr/docs/data-model.md | 12 ++++++++++++ .../sirsoft-gdpr/src/Enums/ConsentSource.php | 12 +++++++++--- .../tests/Unit/ConsentSourceVocabularyParityTest.php | 7 +++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md index 71cec178..27bdf8e3 100644 --- a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixed - 회원탈퇴로 자동 철회된 동의 이력이 관리자 「GDPR 동의 이력」 화면의 출처 필터로 걸러지지 않던 문제를 수정했습니다. +- 쿠키 동의 저장 요청이 서버만 기록해야 하는 출처(회원가입 시 동의)를 직접 지정할 수 있던 문제를 수정했습니다 — 동의 이력의 출처가 실제 동의 경로와 일치합니다. ## [1.0.3] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-gdpr/docs/data-model.md b/plugins/_bundled/sirsoft-gdpr/docs/data-model.md index 123b1cb3..41d25b46 100644 --- a/plugins/_bundled/sirsoft-gdpr/docs/data-model.md +++ b/plugins/_bundled/sirsoft-gdpr/docs/data-model.md @@ -83,6 +83,18 @@ case입니다 — 두 지점 모두 enum이 아닌 `'withdraw'` 리터럴을 직 추가로 정정했습니다. 새 기록 지점을 추가할 때 위치 인자로 리터럴을 넘기면 이 가드가 여전히 못 볼 수 있다는 점을 유의하세요 — 가능하면 `'source' =>`/`'last_source' =>` 형태(배열 키)를 쓰거나 이 테스트의 정규식을 함께 넓힙니다. + +전체 어휘(`ConsentSource::allValues()`)와 **공개 요청이 지정할 수 있는 부분집합** +(`ConsentSource::requestSelectableValues()`)은 다릅니다. `register`(회원가입 시 동의) · +`mypage_renew_all`(정책 개정 후 일괄 재동의) · `withdraw`(회원탈퇴 시 일괄 철회)는 서버가 +스스로 기록하는 경로이므로 `StoreCookieConsentRequest` 의 `Rule::in` 에서 제외됩니다 — +이 엔드포인트는 `optional.sanctum` 이라 비인증 방문자도 도달하므로, 공개 요청이 이 값을 +실을 수 있으면 가입하지도 탈퇴하지도 재동의하지도 않은 사람의 이력이 그렇게 기록됩니다. +동의 이력은 출처가 존재 이유이고, 그렇게 기록되어도 오류도 로그도 남지 않습니다. 반대로 관리자 +동의 이력 화면의 출처 필터(`IndexConsentLogRequest`)는 `allValues()` 를 씁니다: 기록된 +어휘 전부가 필터로 도달 가능해야 하기 때문입니다. 새 case 를 추가할 때 어느 쪽에 속하는지 +판단하고, 제외한다면 `ConsentSourceVocabularyParityTest` 에 제외 단언을 함께 남기세요 — +목록에서 빠뜨려도 단언이 없으면 아무 테스트도 red 가 되지 않습니다. ## Repository diff --git a/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php b/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php index 1ddd135e..d728da27 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php @@ -51,8 +51,15 @@ enum ConsentSource: string /** * 사용자 요청으로 직접 지정할 수 있는 출처 값 목록. * - * `register` / `mypage_renew_all` 은 서버가 스스로 기록하는 경로이므로 - * 공개 요청 본문에서 지정할 수 없습니다. + * `register`(회원가입 시 동의) · `mypage_renew_all`(정책 개정 후 일괄 재동의) · + * `withdraw`(회원탈퇴 시 일괄 철회) 는 서버가 스스로 기록하는 경로이므로 공개 요청 + * 본문에서 지정할 수 없습니다. 이 엔드포인트는 비인증 방문자도 도달하므로, 지정을 + * 허용하면 가입·재동의·탈퇴를 하지 않은 사람의 이력이 그렇게 기록됩니다 — 동의 이력은 + * 출처가 존재 이유이고, 그렇게 기록되어도 오류도 로그도 남지 않습니다. + * + * 새 case 를 추가할 때는 그것이 공개 요청으로 지정 가능한지 판단해 이 목록에 넣거나 + * 빼고, 뺐다면 `ConsentSourceVocabularyParityTest` 에 제외 단언을 함께 추가합니다 — + * 목록에서 빠뜨려도 검증이 없으면 아무 테스트도 red 가 되지 않습니다. * * @return array */ @@ -61,7 +68,6 @@ enum ConsentSource: string return [ self::Banner->value, self::PreferenceCenter->value, - self::Register->value, self::Mypage->value, ]; } diff --git a/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php b/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php index 007110b0..12620501 100644 --- a/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php +++ b/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php @@ -183,6 +183,13 @@ class ConsentSourceVocabularyParityTest extends TestCase $selectable = ConsentSource::requestSelectableValues(); $this->assertNotContains(ConsentSource::MypageRenewAll->value, $selectable); + // 회원탈퇴 철회는 `GdprConsentService::revokeAllOnWithdraw()` 만 기록하는 경로다. + // 공개 요청이 이 값을 실을 수 있으면 탈퇴하지 않은 사용자의 이력이 탈퇴로 기록된다. + $this->assertNotContains(ConsentSource::Withdraw->value, $selectable); + // 가입 동의는 `GdprAuthConsentListener::recordRegisterConsents()` 만 기록하는 + // 경로다. 이 엔드포인트는 비인증 방문자도 도달하므로, 지정을 허용하면 가입하지 + // 않은 방문자의 이력이 가입 동의로 기록된다. + $this->assertNotContains(ConsentSource::Register->value, $selectable); $this->assertContains(ConsentSource::Banner->value, $selectable); } }