From 57c8a31268b423139b2ff58ea384391b164fd8bc Mon Sep 17 00:00:00 2001 From: "Jeong, Se-hyeon" <168955357+gitIt-sehyeon@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:44:39 +0900 Subject: [PATCH] =?UTF-8?q?feat(F-CMN-002):=20rubric:read=20=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=90=EC=82=AC=20=EB=8C=80=EC=83=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=84=A3=EB=8A=94=EB=8B=A4=20=E2=80=94=20=ED=94=84=EB=A1=9D?= =?UTF-8?q?=EC=8B=9C=EC=99=80=20=ED=95=9C=20=EB=AA=B8=EC=9D=B4=EB=8B=A4=20?= =?UTF-8?q?(#609=20=E2=91=A1=20=EC=85=8B=EC=A7=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 명세 §12.1 6번 행이 「COMPL·MGR·ADMIN 읽기 + 감사 대상」으로 정한 것의 뒷쪽 절반이다(#494 9/7 확정). #610 이 앞쪽(역할·범위)을 냈고 여기가 감사다. #612(열람 프록시) 위에 쌓는다. 프록시 없이 먼저 넣으면 everyAuditedActionIsReachable 이 「audited 인데 아무도 안 쓰는 action」으로 잡는다 — #610 에서 그래서 비워 뒀다. 프록시가 있는 지금은 통과한다. 이 줄이 없으면 AuditInterceptor 가 이 호출을 조용히 넘기고(:63), 명세가 감사 대상이라 정한 경로가 기록 없이 배포된다. 두 PR 사이에 그 구간을 두지 않으려고 따로 내지 않고 스택으로 붙인다. AccessPolicyTest 에 단정을 건다 — audited 에서 빼면 빨개지는 것을 확인했다. server 832건 통과. --- server/src/main/resources/rbac_policy.yaml | 9 ++++++++ .../sphinx/security/AccessPolicyTest.java | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/server/src/main/resources/rbac_policy.yaml b/server/src/main/resources/rbac_policy.yaml index e20b4dfd..bf1d6a1f 100644 --- a/server/src/main/resources/rbac_policy.yaml +++ b/server/src/main/resources/rbac_policy.yaml @@ -256,6 +256,15 @@ audited: - admin:role:assign # 상품 등록·추출은 게이트의 분모를 바꾼다 — 누가 언제 했는지가 남아야 한다 (결정 10.36). - product:manage + # ❗**루브릭은 채점 정답표다** — 명세 §12.1 6번 행이 「COMPL·MGR·ADMIN 읽기 + **감사 대상**」 + # 으로 정했다(#494 9/7 확정). 판매 라인에는 애초에 안 열리지만(`rubric:read` 에 SELLER 가 + # 없다), 역할이 있는 사람이 **언제 무엇의 기준을 봤는지**는 남아야 한다 — 7-4 가 역할 부재와 + # 범위 분리를 두 층으로 쌓은 것과 같은 결이다. + # + # 이 줄은 열람 프록시(#475)와 **한 몸이다.** 프록시 없이 먼저 넣으면 + # `everyAuditedActionIsReachable` 이 「audited 인데 아무도 안 쓰는 action」으로 잡는다 — + # 그래서 #610 에서 비워 두고 여기서 붙인다(#609 ② 의 셋째 칸). + - rubric:read # ❗S-04 를 **띄운 사실**이 남아야 한다 (`#76` · 기획 7-2 — "누가 무엇을 봤는지 남는다"). # 비audited 인 `session:interview` 를 재사용하면 그 요건이 안 채워진다(이슈 #214). # diff --git a/server/src/test/java/com/sphinxfin/sphinx/security/AccessPolicyTest.java b/server/src/test/java/com/sphinxfin/sphinx/security/AccessPolicyTest.java index dfff9732..5e9b6cf4 100644 --- a/server/src/test/java/com/sphinxfin/sphinx/security/AccessPolicyTest.java +++ b/server/src/test/java/com/sphinxfin/sphinx/security/AccessPolicyTest.java @@ -395,6 +395,7 @@ void readAndManageAreDistinct() { class RubricIsClosedToSalesLine { private static final Target CATALOG = Target.aggregate(); + private final RbacPolicyFile file = new RbacPolicyFile(); @Test @DisplayName("❗SELLER 는 루브릭을 못 읽는다 — 채점 정답표라 7-4 에 걸린다") @@ -419,6 +420,26 @@ void supervisionAndAdminCanRead() { void custIsNotAReaderHere() { assertThat(policy.permits(new Actor("cust-01", Role.CUST, null), "rubric:read", CATALOG)).isFalse(); } + + /** + * ❗감사 대상이다. 명세 §12.1 6번 행이 "COMPL·MGR·ADMIN 읽기 + 감사 대상" + * 으로 정했다({@code #494} 9/7 확정). 판매 라인에는 애초에 안 열리지만, 역할이 있는 + * 사람이 언제 무엇의 기준을 봤는지는 남아야 한다 — 7-4 가 역할 부재와 범위 + * 분리를 두 층으로 쌓은 것과 같은 결이다. + * + *

이 단정이 {@code rbac_policy.yaml} 의 한 줄을 지킨다. 빠지면 + * {@code AuditInterceptor} 가 이 호출을 조용히 넘긴다({@code :63}) — 로그 + * 0건이 "아무도 안 봤다" 로 읽히고, 그건 이 action 이 감사 대상인 이유를 + * 정면으로 깬다. + */ + @Test + @DisplayName("❗감사 대상이다 — 누가 언제 어느 기준을 봤는지가 남는다 (명세 §12.1 6)") + void readingRubricsIsAudited() { + assertThat(file.audited()) + .as("여기서 빠지면 AuditInterceptor 가 이 호출을 조용히 넘긴다 — " + + "채점 정답표를 열어 본 사실이 아무 데도 안 남는다") + .contains("rubric:read"); + } } @Nested