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); } }