@@ -68,3 +68,10 @@ backoffice:
|
||||
oci-profile: ${BACKOFFICE_AI_OCI_PROFILE:${OCI_PROFILE:DEFAULT}}
|
||||
oci-region: ${BACKOFFICE_AI_OCI_REGION:${POC3_LLM_GPT55_OCI_REGION:}}
|
||||
oci-compartment-id: ${BACKOFFICE_AI_OCI_COMPARTMENT_ID:${OCI_GENAI_COMPARTMENT_ID:}}
|
||||
select-ai:
|
||||
# Cloud AI profiles are schema-owned. This connection must use SGMP_POC,
|
||||
# not the ADMIN connection used by the backoffice control plane.
|
||||
db-url: ${BACKOFFICE_SELECT_AI_DB_URL:}
|
||||
db-username: ${BACKOFFICE_SELECT_AI_DB_USERNAME:}
|
||||
db-password: ${BACKOFFICE_SELECT_AI_DB_PASSWORD:}
|
||||
profile: ${BACKOFFICE_SELECT_AI_PROFILE:SGMP_POC_HAIKU45}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.AuditMapper">
|
||||
<insert id="insert" parameterType="com.cloudhandson.vpdbackoffice.domain.audit.AuditEvent">
|
||||
INSERT INTO hmm_access_audit (
|
||||
event_type, key_id, object_id, status, row_count, error_code, message, created_at
|
||||
INSERT INTO sg_audit_event (
|
||||
audit_id, event_type, key_id, object_id, status, row_count, error_code, message, created_at
|
||||
) VALUES (
|
||||
sg_audit_event_seq.NEXTVAL,
|
||||
#{eventType,jdbcType=VARCHAR},
|
||||
#{keyId,jdbcType=NUMERIC},
|
||||
#{objectId,jdbcType=NUMERIC},
|
||||
|
||||
@@ -68,10 +68,10 @@
|
||||
-->
|
||||
<select id="findPolicyStatuses" resultType="com.cloudhandson.vpdbackoffice.domain.masking.MaskingPolicyStatus">
|
||||
WITH managed_policy AS (
|
||||
SELECT 'KB_CUSTOMERS' AS object_name, 'KB_CUSTOMER_PII_REDACT' AS policy_name FROM dual
|
||||
UNION ALL SELECT 'KB_CLAIMS', 'KB_CLAIM_AMOUNT_REDACT' FROM dual
|
||||
UNION ALL SELECT 'KB_CONTRACTS', 'KB_CONTRACT_PREMIUM_REDACT' FROM dual
|
||||
UNION ALL SELECT 'KB_EXTERNAL_HOLDINGS', 'KB_EXT_HOLDING_REDACT' FROM dual
|
||||
SELECT 'CZN_COMN_USER_MST' AS object_name, 'SG_CZN_USER_REDACT' AS policy_name FROM dual
|
||||
UNION ALL SELECT 'COMN_SALES_USER_MST', 'SG_SALES_USER_REDACT' FROM dual
|
||||
UNION ALL SELECT 'COMN_SALES_TXN', 'SG_SALES_TXN_REDACT' FROM dual
|
||||
UNION ALL SELECT 'COMN_REFUND_TXN', 'SG_REFUND_TXN_REDACT' FROM dual
|
||||
),
|
||||
configured AS (
|
||||
SELECT protected_object.object_name,
|
||||
@@ -80,7 +80,7 @@
|
||||
JOIN sg_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN sg_protected_column protected_column ON protected_column.column_id = link.column_id
|
||||
JOIN sg_protected_object protected_object ON protected_object.object_id = protected_column.object_id
|
||||
WHERE protected_object.owner = 'POC_2'
|
||||
WHERE protected_object.owner = 'SGMP_POC'
|
||||
AND rule.enabled_yn = 'Y'
|
||||
GROUP BY protected_object.object_name
|
||||
),
|
||||
@@ -96,7 +96,7 @@
|
||||
LEFT JOIN redaction_columns policy_column
|
||||
ON policy_column.object_owner = policy.object_owner
|
||||
AND policy_column.object_name = policy.object_name
|
||||
WHERE policy.object_owner = 'POC_2'
|
||||
WHERE policy.object_owner = 'SGMP_POC'
|
||||
GROUP BY policy.object_name, policy.policy_name, policy.enable
|
||||
),
|
||||
missing_columns AS (
|
||||
@@ -110,7 +110,7 @@
|
||||
ON policy_column.object_owner = protected_object.owner
|
||||
AND policy_column.object_name = protected_object.object_name
|
||||
AND policy_column.column_name = protected_column.column_name
|
||||
WHERE protected_object.owner = 'POC_2'
|
||||
WHERE protected_object.owner = 'SGMP_POC'
|
||||
AND rule.enabled_yn = 'Y'
|
||||
AND policy_column.column_name IS NULL
|
||||
GROUP BY protected_object.object_name
|
||||
@@ -120,14 +120,14 @@
|
||||
COUNT(*) AS extra_column_count
|
||||
FROM redaction_columns policy_column
|
||||
JOIN managed_policy managed ON managed.object_name = policy_column.object_name
|
||||
WHERE policy_column.object_owner = 'POC_2'
|
||||
WHERE policy_column.object_owner = 'SGMP_POC'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sg_column_masking_rule link
|
||||
JOIN sg_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN sg_protected_column protected_column ON protected_column.column_id = link.column_id
|
||||
JOIN sg_protected_object protected_object ON protected_object.object_id = protected_column.object_id
|
||||
WHERE protected_object.owner = 'POC_2'
|
||||
WHERE protected_object.owner = 'SGMP_POC'
|
||||
AND protected_object.object_name = policy_column.object_name
|
||||
AND protected_column.column_name = policy_column.column_name
|
||||
AND rule.enabled_yn = 'Y'
|
||||
@@ -138,18 +138,15 @@
|
||||
SELECT policy.object_name,
|
||||
COUNT(*) AS legacy_vpd_column_policy_count
|
||||
FROM all_policies policy
|
||||
WHERE policy.object_owner = 'POC_2'
|
||||
WHERE policy.object_owner = 'SGMP_POC'
|
||||
AND policy.enable = 'YES'
|
||||
AND policy.policy_name IN (
|
||||
'KB_CUST_NM_CLS_POLICY', 'KB_RRN_CLS_POLICY',
|
||||
'KB_PREMIUM_CLS_POLICY',
|
||||
'KB_CLAIM_AMT_CLS_POLICY', 'KB_PAID_AMT_CLS_POLICY',
|
||||
'KB_EXT_INSURER_CLS_POLICY', 'KB_EXT_PRODUCT_GRP_CLS_POLICY',
|
||||
'KB_EXT_PRODUCT_TYPE_CLS_POLICY'
|
||||
'SG_CZN_USER_REDACT', 'SG_SALES_USER_REDACT',
|
||||
'SG_SALES_TXN_REDACT', 'SG_REFUND_TXN_REDACT'
|
||||
)
|
||||
GROUP BY policy.object_name
|
||||
)
|
||||
SELECT 'POC_2' AS owner,
|
||||
SELECT 'SGMP_POC' AS owner,
|
||||
managed.object_name,
|
||||
managed.policy_name,
|
||||
database_policy.enable AS enabled,
|
||||
|
||||
@@ -3,31 +3,36 @@
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.StakeholderMapper">
|
||||
<select id="findTokenSubjects" resultType="com.cloudhandson.vpdbackoffice.domain.stakeholder.StakeholderTokenSubject">
|
||||
SELECT s.user_id AS stakeholder_user_id,
|
||||
s.user_nm AS username,
|
||||
s.role,
|
||||
s.channel,
|
||||
s.access_scope,
|
||||
SELECT TO_CHAR(u.user_id) AS stakeholder_user_id,
|
||||
u.user_name AS username,
|
||||
COALESCE((
|
||||
SELECT MIN(r.role_name)
|
||||
FROM sg_user_role user_role
|
||||
JOIN sg_app_role r ON r.role_id = user_role.role_id
|
||||
WHERE user_role.user_id = u.user_id
|
||||
), 'DATA_AI_OPERATOR') AS role,
|
||||
'BACKOFFICE' AS channel,
|
||||
u.dept_code AS access_scope,
|
||||
u.user_id AS app_user_id
|
||||
FROM poc_2.kb_stakeholders s
|
||||
JOIN cb_app_user u ON u.stakeholder_user_id = s.user_id
|
||||
FROM sg_app_user u
|
||||
WHERE u.active = 'Y'
|
||||
ORDER BY CASE s.role WHEN '지점장' THEN 1 WHEN '설계사' THEN 2 ELSE 3 END,
|
||||
s.channel,
|
||||
s.user_nm,
|
||||
s.user_id
|
||||
ORDER BY role, u.dept_code, u.user_name, u.user_id
|
||||
</select>
|
||||
|
||||
<select id="findTokenSubject" resultType="com.cloudhandson.vpdbackoffice.domain.stakeholder.StakeholderTokenSubject">
|
||||
SELECT s.user_id AS stakeholder_user_id,
|
||||
s.user_nm AS username,
|
||||
s.role,
|
||||
s.channel,
|
||||
s.access_scope,
|
||||
SELECT TO_CHAR(u.user_id) AS stakeholder_user_id,
|
||||
u.user_name AS username,
|
||||
COALESCE((
|
||||
SELECT MIN(r.role_name)
|
||||
FROM sg_user_role user_role
|
||||
JOIN sg_app_role r ON r.role_id = user_role.role_id
|
||||
WHERE user_role.user_id = u.user_id
|
||||
), 'DATA_AI_OPERATOR') AS role,
|
||||
'BACKOFFICE' AS channel,
|
||||
u.dept_code AS access_scope,
|
||||
u.user_id AS app_user_id
|
||||
FROM poc_2.kb_stakeholders s
|
||||
JOIN cb_app_user u ON u.stakeholder_user_id = s.user_id
|
||||
WHERE s.user_id = #{stakeholderUserId,jdbcType=VARCHAR}
|
||||
FROM sg_app_user u
|
||||
WHERE TO_CHAR(u.user_id) = #{stakeholderUserId,jdbcType=VARCHAR}
|
||||
AND u.active = 'Y'
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -728,16 +728,16 @@ function permissionRuleBusinessLabel(type, column, value) {
|
||||
return `토큰 이해관계자 본인 행 (${displayColumn})`;
|
||||
}
|
||||
if (type === 'OWN_CONTRACT') {
|
||||
return `담당 설계사 본인 계약 (${displayColumn})`;
|
||||
return `담당 게임 서비스 범위 (${displayColumn})`;
|
||||
}
|
||||
if (type === 'CHANNEL_CONTRACT') {
|
||||
return `토큰 사용자 채널 계약 (${displayColumn})`;
|
||||
return `토큰 채널 게임 서비스 범위 (${displayColumn})`;
|
||||
}
|
||||
if (type === 'OWN_CUSTOMER') {
|
||||
return `담당 설계사 본인 계약에 연결된 고객/청구/외부보유 (${displayColumn})`;
|
||||
return `담당 게임 사용자·거래 데이터 범위 (${displayColumn})`;
|
||||
}
|
||||
if (type === 'CHANNEL_CUSTOMER') {
|
||||
return `토큰 사용자 채널 계약에 연결된 고객/청구/외부보유 (${displayColumn})`;
|
||||
return `토큰 채널 게임 사용자·거래 데이터 범위 (${displayColumn})`;
|
||||
}
|
||||
if (type === 'STATIC_SQL') {
|
||||
return `정적 SQL 조건: ${value || '조건식 미입력'}`;
|
||||
@@ -813,10 +813,10 @@ function collectWizardPredicates(root) {
|
||||
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'EMP_NO')`;
|
||||
}
|
||||
if (type === 'STAKEHOLDER_SELF') {
|
||||
return `EXISTS (KB_CONTRACTS c: c.${displayColumn} = <현재행>.${displayColumn} AND TOKEN_ROLE = '${value}' AND c.FC_ID = TOKEN_STAKEHOLDER_ID)`;
|
||||
return '조건 코드 STAKEHOLDER_SELF: DB 행 접근 함수가 토큰 컨텍스트와 등록된 업무 관계를 기준으로 조건을 생성';
|
||||
}
|
||||
if (type === 'STAKEHOLDER_CHANNEL') {
|
||||
return `EXISTS (KB_CONTRACTS c: c.${displayColumn} = <현재행>.${displayColumn} AND TOKEN_ROLE = '${value}' AND c.FC_CHANNEL = TOKEN_CHANNEL)`;
|
||||
return '조건 코드 STAKEHOLDER_CHANNEL: DB 행 접근 함수가 토큰 채널과 등록된 업무 관계를 기준으로 조건을 생성';
|
||||
}
|
||||
if (type === 'TOKEN_SUBJECT') {
|
||||
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')`;
|
||||
@@ -828,10 +828,10 @@ function collectWizardPredicates(root) {
|
||||
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL')`;
|
||||
}
|
||||
if (type === 'OWN_CUSTOMER') {
|
||||
return `${displayColumn} IN (SELECT CUST_ID FROM KB_CONTRACTS WHERE FC_ID = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID'))`;
|
||||
return '조건 코드 OWN_CUSTOMER: DB 행 접근 함수가 담당 사용자와 게임 데이터 관계를 기준으로 조건을 생성';
|
||||
}
|
||||
if (type === 'CHANNEL_CUSTOMER') {
|
||||
return `${displayColumn} IN (SELECT CUST_ID FROM KB_CONTRACTS WHERE FC_CHANNEL = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL'))`;
|
||||
return '조건 코드 CHANNEL_CUSTOMER: DB 행 접근 함수가 토큰 채널과 게임 데이터 관계를 기준으로 조건을 생성';
|
||||
}
|
||||
if (type === 'DEPT' || type === 'EMP_NO') {
|
||||
return `${displayColumn} = ${sqlLiteral(value)}`;
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
<thead><tr><th>보호 객체</th><th>DB 정책</th><th>백오피스 활성 컬럼</th><th>DB Redaction 컬럼</th><th>레거시 VPD 컬럼 제어</th><th>DB 정책 활성</th><th>상태 판단</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="status : ${policyStatuses}">
|
||||
<td><code th:text="${status.targetLabel()}">POC_2.KB_CUSTOMERS</code></td>
|
||||
<td><code th:text="${status.policyName()}">KB_CUSTOMER_PII_REDACT</code></td>
|
||||
<td><code th:text="${status.targetLabel()}">SGMP_POC.CZN_COMN_USER_MST</code></td>
|
||||
<td><code th:text="${status.policyName()}">SG_GAME_USER_REDACT</code></td>
|
||||
<td th:text="${status.configuredColumnCount()}">0</td>
|
||||
<td th:text="${status.appliedColumnCount()}">0</td>
|
||||
<td><span class="badge" th:classappend="${status.legacyVpdColumnPolicyCount() == 0} ? ' text-bg-secondary' : ' text-bg-danger'" th:text="${status.legacyVpdColumnPolicyCount() == 0} ? '없음' : ${status.legacyVpdColumnPolicyCount() + '건 활성'}">없음</span></td>
|
||||
@@ -142,17 +142,17 @@
|
||||
|
||||
<section class="content-band">
|
||||
<h2>컬럼 마스킹 규칙 등록</h2>
|
||||
<p class="section-subtitle">업무용 이름을 붙여 템플릿을 재사용합니다. 예: <code>KB_RRN_STANDARD</code>.</p>
|
||||
<p class="section-subtitle">게임 데이터 기준의 업무용 이름을 붙여 템플릿을 재사용합니다. 예: <code>GAME_USER_ID_MASK</code>.</p>
|
||||
<form method="post" action="/masking-rules" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
규칙 코드
|
||||
<input class="form-control" name="ruleCode" maxlength="64" pattern="[A-Za-z][A-Za-z0-9_]{2,63}" required placeholder="KB_RRN_STANDARD">
|
||||
<input class="form-control" name="ruleCode" maxlength="64" pattern="[A-Za-z][A-Za-z0-9_]{2,63}" required placeholder="GAME_USER_ID_MASK">
|
||||
<span class="form-hint">영문·숫자·밑줄만 사용합니다.</span>
|
||||
</label>
|
||||
<label>
|
||||
규칙명
|
||||
<input class="form-control" name="ruleName" maxlength="100" required placeholder="주민번호 기본 마스킹">
|
||||
<input class="form-control" name="ruleName" maxlength="100" required placeholder="게임 사용자 식별자 기본 마스킹">
|
||||
</label>
|
||||
<label>
|
||||
마스킹 템플릿
|
||||
@@ -176,8 +176,8 @@
|
||||
<thead><tr><th>코드</th><th>규칙명</th><th>템플릿</th><th>설명</th><th>상태</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="rule : ${rules}">
|
||||
<td><code th:text="${rule.ruleCode()}">KB_RRN_STANDARD</code></td>
|
||||
<td th:text="${rule.ruleName()}">주민번호 기본 마스킹</td>
|
||||
<td><code th:text="${rule.ruleCode()}">GAME_USER_ID_MASK</code></td>
|
||||
<td th:text="${rule.ruleName()}">게임 사용자 식별자 기본 마스킹</td>
|
||||
<td th:text="${rule.templateLabel()}">값 숨김(NULL)</td>
|
||||
<td th:text="${rule.description() ?: '-'}">설명</td>
|
||||
<td><span class="badge" th:classappend="${rule.enabled()} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${rule.enabledYn()}">Y</span></td>
|
||||
@@ -209,11 +209,11 @@
|
||||
<optgroup th:label="${object.displayName()}" th:if="${!#lists.isEmpty(availableMaskingColumnsByObject[object.objectId()])}">
|
||||
<option th:each="columnName : ${availableMaskingColumnsByObject[object.objectId()]}"
|
||||
th:value="|${object.objectId()}:${columnName}|"
|
||||
th:text="${object.displayName() + '.' + columnName}">POC_2.KB_CONTRACTS.PREMIUM</option>
|
||||
th:text="${object.displayName() + '.' + columnName}">SGMP_POC.COMN_SALES_TXN.GUID</option>
|
||||
</optgroup>
|
||||
</th:block>
|
||||
</select>
|
||||
<span class="form-hint">현재 관리 대상 ASO 정책이 있는 객체만 표시됩니다. 예: <code>POC_2.KB_CONTRACTS.PREMIUM</code>.</span>
|
||||
<span class="form-hint">현재 관리 대상 ASO 정책이 있는 게임 데이터 객체만 표시됩니다. 예: <code>SGMP_POC.COMN_SALES_TXN.GUID</code>.</span>
|
||||
</label>
|
||||
<button class="btn btn-outline-primary" type="submit">대상 컬럼 추가</button>
|
||||
</form>
|
||||
@@ -253,9 +253,9 @@
|
||||
<thead><tr><th>대상 컬럼</th><th>규칙</th><th>템플릿</th><th>백오피스 설정</th><th>DB ASO 적용 상태</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="columnRule : ${columnRules}">
|
||||
<td><code th:text="${columnRule.targetLabel()}">POC_2.KB_CUSTOMERS.RRN_MASKED</code></td>
|
||||
<td th:text="${columnRule.ruleName()}">주민번호 기본 마스킹</td>
|
||||
<td th:text="${columnRule.template().label()}">주민등록번호 부분 마스킹</td>
|
||||
<td><code th:text="${columnRule.targetLabel()}">SGMP_POC.CZN_COMN_USER_MST.USER_ID</code></td>
|
||||
<td th:text="${columnRule.ruleName()}">게임 사용자 식별자 기본 마스킹</td>
|
||||
<td th:text="${columnRule.template().label()}">식별자 부분 마스킹</td>
|
||||
<td><span class="badge" th:classappend="${columnRule.ruleEnabled()} ? ' text-bg-success' : ' text-bg-warning'" th:text="${columnRule.ruleEnabled()} ? '기본 규칙 연결됨' : '규칙 비활성'">기본 규칙 연결됨</span></td>
|
||||
<td th:with="policyStatus=${policyStatusByObjectName[columnRule.objectName()]}">
|
||||
<span class="badge"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<h1>MCP 연동</h1>
|
||||
<details class="explanation-details">
|
||||
<summary>도움말</summary>
|
||||
<p>사용자 Bearer Token을 전달해 GPT-5.4-mini Select AI 자연어 조회를 실행하는 단일 MCP tool을 제공합니다. Select AI는 comment, annotation, constraint 메타데이터를 함께 사용하고, DB에서는 토큰 기준 행 접근 context가 적용됩니다.</p>
|
||||
<p>사용자 Bearer Token을 검증한 뒤 <code>SGMP_POC_HAIKU45</code> Select AI 프로파일로 게임 데이터 Text2SQL을 생성하는 단일 MCP tool을 제공합니다. Select AI는 comment, annotation, constraint 메타데이터를 함께 사용하며, 생성 SQL은 자동 실행하지 않습니다.</p>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<td>
|
||||
<div th:text="${tool.description()}">ORDS 행 접근 조회 도구 설명</div>
|
||||
<small class="text-muted">
|
||||
HTTP <code>Authorization</code> → 행 접근 컨텍스트 · <code>prompt</code> → GPT-5.4-mini Select AI 자연어 질의 · <code>limit</code> → 최대 반환 행 수
|
||||
HTTP <code>Authorization</code> → 활성 PoC 사용자 토큰 검증 · <code>prompt</code> → 게임 데이터 Text2SQL 생성 · 결과 SQL은 검토 후 별도 실행
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -116,10 +116,9 @@
|
||||
<summary>tools/call parameter 예시 보기</summary>
|
||||
<h2>tools/call Arguments</h2>
|
||||
<pre class="code-block">{
|
||||
"prompt": "KB_CLAIMS의 전체 청구 건수를 조회해 줘.",
|
||||
"limit": 50
|
||||
"prompt": "카제나의 최신 BASE_DT 기준 AU(활성 사용자 수)를 조회하는 SQL을 만들어줘."
|
||||
}</pre>
|
||||
<p class="form-hint">등록 tool은 <code>ords.query.kb_select_ai_vpd</code> 하나입니다. HTTP Authorization의 사용자 Bearer Token으로 컨텍스트를 설정한 뒤 GPT-5.4-mini가 comment, annotation, constraint를 참고해 생성한 검증된 읽기 전용 KB 원장 SQL만 실행합니다.</p>
|
||||
<p class="form-hint">등록 tool은 <code>oracle.select_ai.smilegate_game_text2sql</code> 하나입니다. 활성 사용자 Bearer Token을 확인한 뒤 <code>SGMP_POC_HAIKU45</code>가 comment, annotation, constraint를 참고해 읽기 전용 게임 데이터 SQL을 생성합니다. 실행은 자동으로 수행하지 않습니다.</p>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
@@ -153,10 +152,9 @@
|
||||
"id": 3,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "ords.query.kb_select_ai_vpd",
|
||||
"name": "oracle.select_ai.smilegate_game_text2sql",
|
||||
"arguments": {
|
||||
"prompt": "KB_CLAIMS의 전체 청구 건수를 조회해 줘.",
|
||||
"limit": 50
|
||||
"prompt": "카제나의 최신 BASE_DT 기준 AU(활성 사용자 수)를 조회하는 SQL을 만들어줘."
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
|
||||
@@ -159,8 +159,8 @@
|
||||
<thead><tr><th>보호 객체</th><th>DB 정책</th><th>백오피스 활성 컬럼</th><th>DB Redaction 컬럼</th><th>상태</th><th>확인 결과</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="status : ${maskingPolicyStatuses}">
|
||||
<td><code th:text="${status.targetLabel()}">POC_2.KB_CLAIMS</code></td>
|
||||
<td><code th:text="${status.policyName()}">KB_CLAIM_AMOUNT_REDACT</code></td>
|
||||
<td><code th:text="${status.targetLabel()}">SGMP_POC.COMN_SALES_TXN</code></td>
|
||||
<td><code th:text="${status.policyName()}">SG_SALES_TXN_REDACT</code></td>
|
||||
<td th:text="${status.configuredColumnCount()}">0</td>
|
||||
<td th:text="${status.appliedColumnCount()}">0</td>
|
||||
<td><span class="badge" th:classappend="${' ' + status.badgeClass()}" th:text="${status.statusLabel()}">적용됨</span></td>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<details class="explanation-details">
|
||||
<summary>도움말</summary>
|
||||
<p>이 화면은 <strong>행 접근 규칙</strong>만 저장합니다. 저장값은 최종 SQL이 아니라 <code>CB_AGENT_DOC_VPD_FILTER</code>가 읽어서 대상 테이블의 WHERE predicate로 바꾸는 매핑 데이터입니다.</p>
|
||||
<p>컬럼 원문/마스킹은 이 화면에서 처리하지 않습니다. 주민번호, 보험료, 청구금액 같은 컬럼 표시는 <a href="/masking-rules">컬럼 마스킹</a>의 ASO/Data Redaction 정책에서 관리합니다.</p>
|
||||
<p>컬럼 원문/마스킹은 이 화면에서 처리하지 않습니다. 게임 사용자 식별자, 결제금액 같은 민감 컬럼 표시는 <a href="/masking-rules">컬럼 마스킹</a>의 ASO/Data Redaction 정책에서 관리합니다.</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -158,15 +158,15 @@
|
||||
</select>
|
||||
<select class="form-select rule-type-select" name="ruleType">
|
||||
<option value="ALL">ALL</option>
|
||||
<optgroup label="KB · 본인 범위">
|
||||
<option value="OWN_CONTRACT">본인 담당 계약</option>
|
||||
<option value="OWN_CUSTOMER">본인 담당 고객</option>
|
||||
<optgroup label="게임 데이터 · 담당자 범위">
|
||||
<option value="OWN_CONTRACT">담당 게임 서비스</option>
|
||||
<option value="OWN_CUSTOMER">담당 게임 사용자</option>
|
||||
</optgroup>
|
||||
<optgroup label="KB · 채널 범위">
|
||||
<option value="CHANNEL_CONTRACT">토큰 채널 계약</option>
|
||||
<option value="CHANNEL_CUSTOMER">토큰 채널 고객</option>
|
||||
<optgroup label="게임 데이터 · 채널 범위">
|
||||
<option value="CHANNEL_CONTRACT">토큰 채널 게임 서비스</option>
|
||||
<option value="CHANNEL_CUSTOMER">토큰 채널 게임 사용자</option>
|
||||
</optgroup>
|
||||
<optgroup label="KB · 토큰 식별">
|
||||
<optgroup label="게임 데이터 · 토큰 식별">
|
||||
<option value="TOKEN_SUBJECT">토큰 사용자 본인</option>
|
||||
</optgroup>
|
||||
<optgroup label="SQL · 정적 조건">
|
||||
@@ -179,7 +179,7 @@
|
||||
</div>
|
||||
<details class="explanation-details">
|
||||
<summary>행 규칙의 두 가지 적용 방식 보기</summary>
|
||||
<p class="wizard-hint"><strong>조건 코드</strong>는 토큰 context로 치환됩니다. 예를 들어 <code>본인 담당 고객 / CUST_ID</code>는 계약원장에서 토큰 사용자 ID의 담당 고객을 찾아 현재 객체의 <code>CUST_ID</code>에 적용합니다. <strong>정적 SQL 조건식</strong>은 <code>CONTRACT_STATUS = '정상'</code>처럼 현재 객체 컬럼을 사용한 WHERE 절을 그대로 추가합니다. 한 권한 안의 규칙은 모두 AND로 좁혀지고, 서로 다른 역할의 ALLOW 권한은 OR로 합쳐집니다. 역할명은 최종 WHERE 절에 직접 들어가지 않습니다.</p>
|
||||
<p class="wizard-hint"><strong>조건 코드</strong>는 토큰 context와 DB에 등록된 업무 관계를 바탕으로 행 접근 함수가 해석합니다. 관계가 없는 게임 데이터 객체에는 조건 코드를 억지로 적용하지 말고, <strong>정적 SQL 조건식</strong>으로 <code>GAME_ID = 'CZN'</code>처럼 실제 컬럼을 사용하세요. 한 권한 안의 규칙은 모두 AND로 좁혀지고, 서로 다른 역할의 ALLOW 권한은 OR로 합쳐집니다.</p>
|
||||
<p class="wizard-hint"><strong>컬럼 원문/마스킹은 제외했습니다.</strong> 행 접근 필터는 행만 남기고, ASO/Data Redaction이 허용된 행 안에서 컬럼을 원문 또는 마스킹으로 반환합니다.</p>
|
||||
</details>
|
||||
<details class="explanation-details">
|
||||
@@ -196,28 +196,28 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>TOKEN_SUBJECT</code></td>
|
||||
<td>토큰으로 식별된 이해관계자 본인 행</td>
|
||||
<td><code>USER_ID = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')</code></td>
|
||||
<td>토큰으로 식별된 운영 사용자 범위</td>
|
||||
<td>대상 객체가 토큰 주체 식별 컬럼을 가질 때만 DB 행 접근 함수가 생성</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>OWN_CONTRACT</code></td>
|
||||
<td>토큰 사용자가 담당 설계사인 계약</td>
|
||||
<td><code>FC_ID = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')</code></td>
|
||||
<td>토큰 사용자가 담당하는 게임 서비스 범위</td>
|
||||
<td>등록된 업무 관계가 있을 때만 DB 행 접근 함수가 생성</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>CHANNEL_CONTRACT</code></td>
|
||||
<td>토큰 사용자의 채널에 속한 계약</td>
|
||||
<td><code>FC_CHANNEL = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL')</code></td>
|
||||
<td>토큰 채널에 속한 게임 서비스 범위</td>
|
||||
<td>등록된 업무 관계가 있을 때만 DB 행 접근 함수가 생성</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>OWN_CUSTOMER</code> / <code>CHANNEL_CUSTOMER</code></td>
|
||||
<td>계약원장에서 담당자 또는 채널 기준으로 연결되는 고객·청구·담보·외부보유 행</td>
|
||||
<td><code>EXISTS (SELECT 1 FROM POC_2.KB_CONTRACTS ...)</code></td>
|
||||
<td>게임 서비스·사용자 기준으로 연결되는 게임 로그·판매·환불 데이터 범위</td>
|
||||
<td>대상 테이블의 실제 키 관계를 DB 행 접근 함수가 검증한 뒤 생성</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>STATIC_SQL</code></td>
|
||||
<td>현재 객체 컬럼으로 표현한 고정 조건</td>
|
||||
<td><code>CONTRACT_STATUS = '정상'</code>처럼 검증된 현재 객체 컬럼 조건</td>
|
||||
<td><code>GAME_ID = 'CZN'</code>처럼 검증된 현재 객체 컬럼 조건</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
<tbody>
|
||||
<tr th:each="item : ${scripts}" th:classappend="${selectedScript != null and item.scriptId() == selectedScript.scriptId()} ? ' table-primary'">
|
||||
<td><span class="badge text-bg-light" th:text="${item.category()}">ASO / 마스킹</span></td>
|
||||
<td><code th:text="${item.fileName()}">62_kb_aso_masking_backoffice_metadata.sql</code></td>
|
||||
<td><code th:text="${item.fileName()}">71_sg_identity_administration.sql</code></td>
|
||||
<td>
|
||||
<strong th:text="${item.title()}">컬럼 마스킹 규칙 메타데이터</strong>
|
||||
<strong th:text="${item.title()}">사용자·그룹·역할 관리 모델</strong>
|
||||
<div class="form-hint" th:text="${item.description()}">설명</div>
|
||||
</td>
|
||||
<td><a class="btn btn-sm rw-btn-secondary" th:href="@{/security-sql-scripts(script=${item.scriptId()})}">원문 보기</a></td>
|
||||
@@ -45,13 +45,13 @@
|
||||
<section class="content-band" th:if="${selectedScript}">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="badge text-bg-secondary" th:text="${selectedScript.category()}">ASO / 마스킹</span>
|
||||
<h2 class="mt-2" th:text="${selectedScript.title()}">컬럼 마스킹 규칙 메타데이터</h2>
|
||||
<span class="badge text-bg-secondary" th:text="${selectedScript.category()}">Smilegate 권한</span>
|
||||
<h2 class="mt-2" th:text="${selectedScript.title()}">사용자·그룹·역할 관리 모델</h2>
|
||||
<p class="section-subtitle" th:text="${selectedScript.description()}">설명</p>
|
||||
</div>
|
||||
<code th:text="${selectedScript.fileName()}">62_kb_aso_masking_backoffice_metadata.sql</code>
|
||||
<code th:text="${selectedScript.fileName()}">71_sg_identity_administration.sql</code>
|
||||
</div>
|
||||
<p class="form-hint">Git source: <code th:text="${'sql/adb/' + selectedScript.fileName()}">sql/adb/62_kb_aso_masking_backoffice_metadata.sql</code>. 실제 DB 배포본은 <a href="/vpd-filter-runtime">행 접근 필터 구조</a> 및 DB 배포 이력과 함께 확인하세요.</p>
|
||||
<p class="form-hint">Git source: <code th:text="${'sql/adb/' + selectedScript.fileName()}">sql/adb/71_sg_identity_administration.sql</code>. 실제 DB 배포본은 <a href="/vpd-filter-runtime">행 접근 필터 구조</a> 및 DB 배포 이력과 함께 확인하세요.</p>
|
||||
<form hx-post="/security-sql-scripts/explanation" hx-target="#security-sql-explanation" hx-swap="innerHTML" class="mb-3">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<input type="hidden" name="script" th:value="${selectedScript.scriptId()}">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('HMM 액세스 토큰')}"></head>
|
||||
<head th:replace="~{fragments/layout :: head('Smilegate 액세스 토큰')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>HMM 액세스 토큰</h1>
|
||||
<h1>Smilegate 액세스 토큰</h1>
|
||||
<details class="explanation-details">
|
||||
<summary>도움말</summary>
|
||||
<p>HMM HR 직원에게 접근 토큰을 발급합니다. 토큰 원문은 한 번만 표시하며, DB에는 SHA-256 해시와 식별용 prefix만 보관합니다.</p>
|
||||
<p>Data & AI PoC 도구 사용자에게 접근 토큰을 발급합니다. 토큰 원문은 한 번만 표시하며, DB에는 SHA-256 해시와 식별용 prefix만 보관합니다.</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>직원 토큰 발급</h2>
|
||||
<p class="section-subtitle">활성 HMM 직원에게 토큰을 발급합니다. 직접 역할과 접근 그룹 역할은 토큰 재발급 없이 조회 시점에 반영됩니다.</p>
|
||||
<h2>도구 사용자 토큰 발급</h2>
|
||||
<p class="section-subtitle">활성 Data & AI PoC 사용자에게 토큰을 발급합니다. 직접 역할과 접근 그룹 역할은 토큰 재발급 없이 조회 시점에 반영됩니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/tokens" class="form-grid">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
HMM 직원
|
||||
도구 사용자
|
||||
<select class="form-select" name="userId" required>
|
||||
<option value="" selected disabled>토큰을 발급할 직원 선택</option>
|
||||
<option th:each="user : ${users}"
|
||||
@@ -48,7 +48,7 @@
|
||||
</label>
|
||||
<label>
|
||||
용도 메모
|
||||
<input class="form-control" name="description" maxlength="200" placeholder="예: HR 권한 확인">
|
||||
<input class="form-control" name="description" maxlength="200" placeholder="예: 게임 데이터 AI 질의 검증">
|
||||
</label>
|
||||
<button class="btn rw-btn-primary" type="submit">검증 세션 발급</button>
|
||||
</form>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<select class="form-select" name="userId" required>
|
||||
<option value="">선택하세요</option>
|
||||
<option th:each="user : ${users}" th:value="${user.userId()}" th:attr="data-user-label=${user.username()}"
|
||||
th:text="${user.username() + ' (' + user.empNo() + ')'}">KB_VPD_ADMIN</option>
|
||||
th:text="${user.username() + ' (' + user.empNo() + ')'}">sg-teamlead (SG-001)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
@@ -39,7 +39,7 @@
|
||||
data-result=${columnRule.template().previewResult()},
|
||||
data-aso-function=${columnRule.template().asoFunction()},
|
||||
data-context=${'MR_' + columnRule.columnId()}"
|
||||
th:text="${columnRule.targetLabel() + ' · ' + columnRule.ruleLabel()}">KB_CUSTOMERS.RRN_MASKED</option>
|
||||
th:text="${columnRule.targetLabel() + ' · ' + columnRule.ruleLabel()}">SGMP_POC.CZN_COMN_USER_MST.GUID</option>
|
||||
</select>
|
||||
</label>
|
||||
<div>
|
||||
@@ -85,9 +85,9 @@
|
||||
<thead><tr><th>사용자</th><th>대상 컬럼</th><th>기본 규칙</th><th>원문 표시 상태</th><th>상태</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="userRule : ${userRules}">
|
||||
<td th:text="${userRule.username()}">KB_VPD_ADMIN</td>
|
||||
<td><code th:text="${userRule.targetLabel()}">POC_2.KB_CUSTOMERS.RRN_MASKED</code></td>
|
||||
<td th:text="${userRule.ruleLabel()}">주민번호 기본 마스킹 · 주민등록번호 부분 마스킹</td>
|
||||
<td th:text="${userRule.username()}">sg-teamlead</td>
|
||||
<td><code th:text="${userRule.targetLabel()}">SGMP_POC.CZN_COMN_USER_MST.GUID</code></td>
|
||||
<td th:text="${userRule.ruleLabel()}">게임 사용자 식별자 기본 마스킹 · 식별번호 부분 마스킹</td>
|
||||
<td><span class="badge" th:classappend="${userRule.unmasked()} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${userRule.decisionLabel()}">원문 표시 예외</span></td>
|
||||
<td th:text="${userRule.activeYn()}">Y</td>
|
||||
<td>
|
||||
|
||||
@@ -82,8 +82,8 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="policy : ${policies}">
|
||||
<td><code th:text="${policy.objectDisplayName()}">POC_2.KB_CONTRACTS</code></td>
|
||||
<td><code th:text="${policy.policyName()}">KB_KB_CONTRACTS_ROW_POLICY</code></td>
|
||||
<td><code th:text="${policy.objectDisplayName()}">SGMP_POC.CZN_COMN_USER_MST</code></td>
|
||||
<td><code th:text="${policy.policyName()}">SG_CZN_USER_ROW_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>
|
||||
@@ -105,7 +105,7 @@
|
||||
<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>
|
||||
<tr><td>컬럼 보호</td><td>행 필터(VPD)와 게임 사용자 식별자·거래 식별자의 ASO/Data Redaction 마스킹을 분리해 확인합니다.</td><td><a href="/masking-rules">컬럼 마스킹</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user