<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/layout :: head('행 접근 필터 구조')}"></head>
<body>
<nav th:replace="~{fragments/layout :: nav}"></nav>
<main class="container py-4">
  <section class="page-title">
    <span class="badge text-bg-primary">운영 기준</span>
    <h1 class="mt-2">행 접근 필터 구조</h1>
    <details class="explanation-details">
      <summary>도움말</summary>
      <p>기본 동적 필터 <code>CB_AGENT_DOC_VPD_FILTER</code>의 연결 상태와 현재 DB에 배포된 함수를 읽기 전용으로 보여줍니다. 행 접근 변경은 <a href="/permissions">행 접근 규칙</a>에서 수행합니다.</p>
      <section class="content-band mt-3 mb-0">
        <h2>행 필터(VPD) 적용 흐름</h2>
        <p><strong>Oracle VPD 정책 함수는 Bearer Token을 직접 해석하지 않습니다.</strong> 앞 단계의 <code>cb_agent_ctx_pkg.set_user_by_bearer</code>가 만든 <code>CB_AGENT_CTX</code>를 읽어, 현재 SELECT에 붙일 WHERE predicate를 생성합니다.</p>
        <div class="diagram-canvas">
          <svg class="product-flow-diagram" viewBox="0 0 1120 410" role="img" aria-labelledby="vpd-help-flow-title vpd-help-flow-desc">
            <title id="vpd-help-flow-title">Bearer 토큰 기반 행 필터 적용 흐름</title>
            <desc id="vpd-help-flow-desc">토큰으로 설정된 사용자 컨텍스트를 행 접근 함수가 읽고 역할과 행 접근 규칙을 WHERE 조건으로 변환해, 허용된 행만 SELECT 결과에 남긴다.</desc>
            <defs>
              <marker id="flow-arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth"><path d="M0,0 L0,6 L9,3 z"/></marker>
            </defs>
            <path class="diagram-link" marker-end="url(#flow-arrow)" d="M185 92 H225"/>
            <path class="diagram-link" marker-end="url(#flow-arrow)" d="M405 92 H445"/>
            <path class="diagram-link" marker-end="url(#flow-arrow)" d="M625 92 H665"/>
            <path class="diagram-link" marker-end="url(#flow-arrow)" d="M845 92 H885"/>
            <path class="diagram-link diagram-link-secondary" marker-end="url(#flow-arrow)" d="M745 212 V144"/>
            <path class="diagram-link" marker-end="url(#flow-arrow)" d="M977 136 V315"/>
            <path class="diagram-link" marker-end="url(#flow-arrow)" d="M900 136 V280 H755 V315"/>
            <g class="diagram-node"><rect x="25" y="48" width="160" height="88" rx="12"/><text x="105" y="79" class="diagram-node-title">Bearer Token</text><text x="105" y="106" class="diagram-node-detail">ORDS 요청</text></g>
            <g class="diagram-node"><rect x="225" y="48" width="180" height="88" rx="12"/><text x="315" y="76" class="diagram-node-title">토큰 검증·해석</text><text x="315" y="103" class="diagram-node-detail">set_user_by_bearer</text><text x="315" y="120" class="diagram-node-note">실패 시 context 정리</text></g>
            <g class="diagram-node"><rect x="445" y="48" width="180" height="88" rx="12"/><text x="535" y="76" class="diagram-node-title">CB_AGENT_CTX</text><text x="535" y="103" class="diagram-node-detail">USER_ID · DEPT_CODE · EMP_NO</text><text x="535" y="120" class="diagram-node-note">STAKEHOLDER_USER_ID · CHANNEL</text></g>
            <g class="diagram-node diagram-node-accent"><rect x="665" y="48" width="180" height="88" rx="12"/><text x="755" y="76" class="diagram-node-title">행 접근 함수 호출</text><text x="755" y="103" class="diagram-node-detail">CB_AGENT_DOC_VPD_FILTER</text><text x="755" y="120" class="diagram-node-note">p_object = 조회 대상</text></g>
            <g class="diagram-node diagram-node-accent"><rect x="885" y="48" width="185" height="88" rx="12"/><text x="977" y="76" class="diagram-node-title">WHERE predicate</text><text x="977" y="103" class="diagram-node-detail">ALLOW / DENY 결합</text><text x="977" y="120" class="diagram-node-note">원래 SELECT에 자동 추가</text></g>
            <g class="diagram-node diagram-node-data"><rect x="590" y="212" width="310" height="72" rx="12"/><text x="745" y="239" class="diagram-node-title">역할·권한·규칙 메타데이터</text><text x="745" y="263" class="diagram-node-detail">cb_user_role · cb_group_role · cb_permission_rule</text></g>
            <g class="diagram-node"><rect x="665" y="315" width="180" height="64" rx="12"/><text x="755" y="341" class="diagram-node-title">ALLOW 없음 / USER_ID 없음</text><text x="755" y="363" class="diagram-node-detail">RETURN 1 = 0 · 행 없음</text></g>
            <g class="diagram-node diagram-node-data"><rect x="885" y="315" width="185" height="64" rx="12"/><text x="977" y="341" class="diagram-node-title">ALLOW 조건 충족</text><text x="977" y="363" class="diagram-node-detail">조건에 맞는 행만 반환</text></g>
          </svg>
        </div>
        <p class="form-hint mb-0"><code>rule_type</code>별 WHERE 조각은 권한 내부에서 AND, ALLOW 권한끼리는 OR, DENY 권한은 최종적으로 <code>AND NOT (...)</code>으로 결합됩니다. ASO는 같은 context의 <code>MR_&lt;column_id&gt;</code>로 <em>컬럼 표시</em>를 정하고, 행 접근 필터는 <code>USER_ID</code>·<code>DEPT_CODE</code>·<code>EMP_NO</code>·<code>STAKEHOLDER_*</code>로 <em>보이는 행</em>을 정합니다.</p>
      </section>
    </details>
  </section>

  <section class="alert alert-warning" th:if="${runtimeError}">
    <strong th:text="${runtimeError.title()}">DB 연결 설정이 필요합니다.</strong>
    <span th:text="${runtimeError.message()}">DB 연결을 확인할 수 없습니다.</span>
  </section>
  <section class="alert alert-danger" th:if="${errorMessage}" th:text="${errorMessage}">조회할 수 없습니다.</section>

  <section class="content-band">
    <h2>실행 흐름</h2>
    <div class="macro-micro-grid">
      <div>
        <h3>1. 신뢰 컨텍스트 설정</h3>
        <p>ORDS가 Bearer Token을 검증하고 <code>CB_AGENT_CTX</code>에 사용자·이해관계자·채널 정보를 설정합니다.</p>
      </div>
      <div>
        <h3>2. 권한과 규칙 계산</h3>
        <p>직접 역할과 활성 그룹 역할의 SELECT 권한을 읽습니다. 한 권한의 규칙은 AND, 여러 ALLOW 권한은 OR로 결합합니다.</p>
      </div>
      <div>
        <h3>3. DB WHERE 절 적용</h3>
        <p>Oracle VPD 정책이 반환 predicate를 원래 SELECT에 자동 적용합니다. 권한이 없으면 <code>1 = 0</code>으로 행 접근을 막는 것이 기준입니다.</p>
      </div>
    </div>
    <p class="form-hint mt-3 mb-0">행 접근은 이 행 필터(VPD)가 담당합니다. 컬럼 마스킹과 원문 표시 허용 사용자는 <a href="/masking-rules">컬럼 마스킹</a>의 ASO/Data Redaction에서만 제어합니다.</p>
  </section>

  <section class="content-band">
    <div class="section-heading">
      <div>
        <h2>현재 연결된 행 접근 정책(VPD)</h2>
        <p class="section-subtitle">기본 Filter를 실제로 호출하는 보호 객체 정책입니다.</p>
      </div>
      <span class="badge text-bg-secondary" th:text="${#lists.size(policies)}">0</span>
    </div>
    <div class="table-responsive">
      <table class="table table-sm align-middle">
        <thead>
        <tr><th>Object</th><th>Policy</th><th>Statement</th><th>상태</th><th>Filter</th></tr>
        </thead>
        <tbody>
        <tr th:each="policy : ${policies}">
          <td><code th:text="${policy.objectDisplayName()}">OWNER.OBJECT_NAME</code></td>
          <td><code th:text="${policy.policyName()}">ROW_ACCESS_POLICY</code></td>
          <td th:text="${policy.statementTypes()}">SELECT</td>
          <td><span class="badge" th:classappend="${policy.enabled() == 'YES'} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${policy.enabled() == 'YES'} ? '적용됨' : '중지됨'">적용됨</span></td>
          <td><code th:text="${policy.functionDisplayName()}">ADMIN.CB_AGENT_DOC_VPD_FILTER</code></td>
        </tr>
        <tr th:if="${#lists.isEmpty(policies)}">
          <td colspan="5" class="text-muted">기본 행 접근 Filter가 연결된 보호 객체를 찾지 못했습니다.</td>
        </tr>
        </tbody>
      </table>
    </div>
  </section>

  <section class="content-band">
    <h2>운영 점검 기준</h2>
    <div class="table-responsive">
      <table class="table table-sm align-middle">
        <thead><tr><th>점검 항목</th><th>확인 기준</th><th>조치 위치</th></tr></thead>
        <tbody>
        <tr><td>토큰 신뢰 경계</td><td>Bearer Token은 ORDS에서 검증하고 요청 종료 시 컨텍스트를 정리합니다.</td><td>ORDS Handler / <code>CB_AGENT_CTX_PKG</code></td></tr>
        <tr><td>권한 결합</td><td>권한 내부 규칙은 AND, ALLOW 권한은 OR, DENY 조건은 최종적으로 제외합니다.</td><td><a href="/permissions">행 접근 규칙</a></td></tr>
        <tr><td>오류·미권한</td><td>유효한 컨텍스트나 ALLOW 권한이 없으면 행 접근은 차단돼야 합니다.</td><td><a href="/probe">접근 검증</a></td></tr>
        <tr><td>컬럼 보호</td><td>행 필터(VPD)와 게임 사용자 식별자·거래 식별자의 ASO/Data Redaction 마스킹을 분리해 확인합니다.</td><td><a href="/masking-rules">컬럼 마스킹</a></td></tr>
        </tbody>
      </table>
    </div>
  </section>

  <section class="content-band">
    <h2>배포된 함수 소스</h2>
    <p class="section-subtitle">현재 DB의 <code>ALL_SOURCE</code>에서 읽은 소스입니다. 형상 기준 SQL과 다르면 배포 이력을 확인하세요.</p>
    <section class="probe-exchange" th:if="${source}">
      <h3><span th:text="${source.owner() + '.' + source.objectName()}">ADMIN.CB_AGENT_DOC_VPD_FILTER</span> <span class="text-muted" th:text="${' / ' + source.objectType()}"> / FUNCTION</span></h3>
      <pre th:if="${source.found()}" th:text="${source.source()}"></pre>
      <pre th:unless="${source.found()}">ALL_SOURCE에서 조회 가능한 소스가 없습니다. DB 연결 계정의 source 조회 권한을 확인하세요.</pre>
    </section>
    <div th:unless="${source}" class="text-muted">기본 Filter source를 조회할 연결 정보를 찾지 못했습니다.</div>
  </section>
</main>
</body>
</html>
