feat #466: add deny permission effect
This commit is contained in:
@@ -7,6 +7,7 @@ public record PermissionSet(
|
||||
long roleId,
|
||||
long objectId,
|
||||
String action,
|
||||
String permissionEffect,
|
||||
List<PermissionRule> rules,
|
||||
List<String> visibleColumns
|
||||
) {
|
||||
|
||||
@@ -9,6 +9,7 @@ public record PermissionSetCommand(
|
||||
@Positive long roleId,
|
||||
@Positive long objectId,
|
||||
@NotBlank String action,
|
||||
String permissionEffect,
|
||||
@NotEmpty List<RuleCommand> rules,
|
||||
List<String> visibleColumns
|
||||
) {
|
||||
|
||||
@@ -7,6 +7,7 @@ public record PermissionView(
|
||||
long objectId,
|
||||
String objectName,
|
||||
String action,
|
||||
String permissionEffect,
|
||||
String rules,
|
||||
String visibleColumns,
|
||||
String filterPreview,
|
||||
|
||||
@@ -36,10 +36,14 @@ public interface PermissionMapper {
|
||||
void insertPermission(@Param("permissionId") long permissionId,
|
||||
@Param("roleId") long roleId,
|
||||
@Param("objectId") long objectId,
|
||||
@Param("action") String action);
|
||||
@Param("action") String action,
|
||||
@Param("permissionEffect") String permissionEffect);
|
||||
|
||||
void updatePermissionAction(@Param("permissionId") long permissionId, @Param("action") String action);
|
||||
|
||||
void updatePermissionEffect(@Param("permissionId") long permissionId,
|
||||
@Param("permissionEffect") String permissionEffect);
|
||||
|
||||
void deleteRules(@Param("permissionId") long permissionId);
|
||||
|
||||
void insertRule(PermissionRule rule);
|
||||
|
||||
@@ -56,9 +56,12 @@ public class BackofficeSchemaService {
|
||||
perm_id NUMBER PRIMARY KEY,
|
||||
role_id NUMBER NOT NULL,
|
||||
target_name VARCHAR2(128) NOT NULL,
|
||||
action_name VARCHAR2(30) DEFAULT 'SELECT' NOT NULL
|
||||
action_name VARCHAR2(30) DEFAULT 'SELECT' NOT NULL,
|
||||
permission_effect VARCHAR2(10) DEFAULT 'ALLOW' NOT NULL
|
||||
)
|
||||
""");
|
||||
addColumn(results, "cb_permission", "permission_effect",
|
||||
"ALTER TABLE cb_permission ADD (permission_effect VARCHAR2(10) DEFAULT 'ALLOW' NOT NULL)");
|
||||
createTable(results, "cb_permission_rule", """
|
||||
CREATE TABLE cb_permission_rule (
|
||||
rule_id NUMBER PRIMARY KEY,
|
||||
|
||||
@@ -22,6 +22,7 @@ public class PermissionService {
|
||||
private static final Set<String> RULE_TYPES = Set.of("ALL", "=", "!=", "MY_DEPT", "SELF", "DEPT", "EMP_NO");
|
||||
private static final Set<String> VALUE_REQUIRED_RULE_TYPES = Set.of("=", "!=", "DEPT", "EMP_NO");
|
||||
private static final Set<String> DEFAULT_COLUMN_RULE_TYPES = Set.of("MY_DEPT", "SELF", "DEPT", "EMP_NO");
|
||||
private static final Set<String> PERMISSION_EFFECTS = Set.of("ALLOW", "DENY");
|
||||
|
||||
private final PermissionMapper permissionMapper;
|
||||
private final ProtectedObjectService protectedObjectService;
|
||||
@@ -69,6 +70,7 @@ public class PermissionService {
|
||||
if (!"SELECT".equalsIgnoreCase(command.action())) {
|
||||
throw new AppException("초기 구현에서는 SELECT 권한만 저장할 수 있습니다.");
|
||||
}
|
||||
String permissionEffect = normalizePermissionEffect(command.permissionEffect());
|
||||
AppRole role = permissionMapper.findRole(command.roleId());
|
||||
if (role == null) {
|
||||
throw new AppException("역할을 찾을 수 없습니다.");
|
||||
@@ -80,9 +82,10 @@ public class PermissionService {
|
||||
Long existingId = permissionMapper.findPermissionId(command.roleId(), command.objectId());
|
||||
long permissionId = existingId == null ? permissionMapper.nextPermissionId() : existingId;
|
||||
if (existingId == null) {
|
||||
permissionMapper.insertPermission(permissionId, command.roleId(), command.objectId(), "SELECT");
|
||||
permissionMapper.insertPermission(permissionId, command.roleId(), command.objectId(), "SELECT", permissionEffect);
|
||||
} else {
|
||||
permissionMapper.updatePermissionAction(permissionId, "SELECT");
|
||||
permissionMapper.updatePermissionEffect(permissionId, permissionEffect);
|
||||
}
|
||||
|
||||
permissionMapper.deleteRules(permissionId);
|
||||
@@ -107,7 +110,7 @@ public class PermissionService {
|
||||
"PERMISSION_SAVED", null, command.objectId(), "SUCCESS", null, null,
|
||||
"roleId=" + command.roleId()
|
||||
));
|
||||
return new PermissionSet(permissionId, command.roleId(), command.objectId(), "SELECT", List.of(), List.of());
|
||||
return new PermissionSet(permissionId, command.roleId(), command.objectId(), "SELECT", permissionEffect, List.of(), List.of());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -185,6 +188,14 @@ public class PermissionService {
|
||||
return clean(value).toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizePermissionEffect(String value) {
|
||||
String effect = clean(value).isBlank() ? "ALLOW" : normalize(value);
|
||||
if (!PERMISSION_EFFECTS.contains(effect)) {
|
||||
throw new AppException("허용되지 않은 권한 효과입니다: " + effect);
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
|
||||
private String normalizeNullable(String value) {
|
||||
String cleaned = clean(value);
|
||||
return cleaned.isBlank() ? null : cleaned.toUpperCase(Locale.ROOT);
|
||||
|
||||
@@ -102,6 +102,7 @@ public class PermissionController {
|
||||
public String save(
|
||||
@RequestParam long roleId,
|
||||
@RequestParam String objectRef,
|
||||
@RequestParam(defaultValue = "ALLOW") String permissionEffect,
|
||||
@RequestParam(required = false) List<String> ruleColumn,
|
||||
@RequestParam List<String> ruleType,
|
||||
@RequestParam(required = false) List<String> ruleValue,
|
||||
@@ -113,6 +114,7 @@ public class PermissionController {
|
||||
roleId,
|
||||
objectId,
|
||||
"SELECT",
|
||||
permissionEffect,
|
||||
buildRules(ruleColumn, ruleType, ruleValue),
|
||||
splitColumns(visibleColumns)
|
||||
));
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
o.object_id,
|
||||
p.target_name AS object_name,
|
||||
p.action_name AS action,
|
||||
NVL(p.permission_effect, 'ALLOW') AS permission_effect,
|
||||
LISTAGG(
|
||||
NVL2(pr.rule_column, pr.rule_column || ' ', '') ||
|
||||
pr.rule_type ||
|
||||
@@ -74,7 +75,7 @@
|
||||
JOIN cb_app_role r ON r.role_id = p.role_id
|
||||
LEFT JOIN cb_protected_object o ON o.object_name = p.target_name
|
||||
LEFT JOIN cb_permission_rule pr ON pr.perm_id = p.perm_id
|
||||
GROUP BY p.perm_id, r.role_id, r.role_name, o.object_id, p.target_name, p.action_name
|
||||
GROUP BY p.perm_id, r.role_id, r.role_name, o.object_id, p.target_name, p.action_name, p.permission_effect
|
||||
ORDER BY r.role_name, p.target_name
|
||||
</select>
|
||||
|
||||
@@ -104,7 +105,8 @@
|
||||
SELECT p.perm_id AS permission_id,
|
||||
p.role_id,
|
||||
o.object_id,
|
||||
p.action_name AS action
|
||||
p.action_name AS action,
|
||||
NVL(p.permission_effect, 'ALLOW') AS permission_effect
|
||||
FROM cb_permission p
|
||||
JOIN cb_protected_object o ON o.object_name = p.target_name
|
||||
WHERE p.role_id = #{roleId}
|
||||
@@ -120,8 +122,8 @@
|
||||
</select>
|
||||
|
||||
<insert id="insertPermission">
|
||||
INSERT INTO cb_permission (perm_id, role_id, target_name, action_name)
|
||||
SELECT #{permissionId}, #{roleId}, object_name, #{action}
|
||||
INSERT INTO cb_permission (perm_id, role_id, target_name, action_name, permission_effect)
|
||||
SELECT #{permissionId}, #{roleId}, object_name, #{action}, #{permissionEffect}
|
||||
FROM cb_protected_object
|
||||
WHERE object_id = #{objectId}
|
||||
</insert>
|
||||
@@ -132,6 +134,12 @@
|
||||
WHERE perm_id = #{permissionId}
|
||||
</update>
|
||||
|
||||
<update id="updatePermissionEffect">
|
||||
UPDATE cb_permission
|
||||
SET permission_effect = #{permissionEffect}
|
||||
WHERE perm_id = #{permissionId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteRules">
|
||||
DELETE FROM cb_permission_rule
|
||||
WHERE perm_id = #{permissionId}
|
||||
|
||||
@@ -37,6 +37,13 @@
|
||||
</optgroup>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
권한 효과
|
||||
<select class="form-select" name="permissionEffect" required>
|
||||
<option value="ALLOW">허용</option>
|
||||
<option value="DENY">거부</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="span-2">
|
||||
<div class="field-block-title">행 규칙</div>
|
||||
<div id="rowRuleList" class="rule-list">
|
||||
@@ -80,6 +87,7 @@
|
||||
<th>역할</th>
|
||||
<th>테이블/뷰</th>
|
||||
<th>Action</th>
|
||||
<th>Effect</th>
|
||||
<th>행 규칙</th>
|
||||
<th>적용 필터</th>
|
||||
<th>NULL 제외 컬럼</th>
|
||||
@@ -92,6 +100,11 @@
|
||||
<td th:text="${permission.roleName()}">HR_DEPT_ROLE</td>
|
||||
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
||||
<td th:text="${permission.action()}">SELECT</td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
th:classappend="${permission.permissionEffect() == 'DENY'} ? ' text-bg-danger' : ' text-bg-success'"
|
||||
th:text="${permission.permissionEffect()}">ALLOW</span>
|
||||
</td>
|
||||
<td th:text="${permission.rules()} ?: '-'">ALL</td>
|
||||
<td><pre class="table-pre" th:text="${permission.filterPreview()} ?: '-'">ALL ROWS</pre></td>
|
||||
<td th:text="${permission.visibleColumns()} ?: '-'">CONTENTS</td>
|
||||
@@ -104,7 +117,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(permissions)}">
|
||||
<td colspan="8" class="text-muted">등록된 권한이 없습니다.</td>
|
||||
<td colspan="9" class="text-muted">등록된 권한이 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -52,6 +52,7 @@ class PermissionServiceTest {
|
||||
10L,
|
||||
1L,
|
||||
"SELECT",
|
||||
"ALLOW",
|
||||
List.of(new RuleCommand(null, "ALL", null), new RuleCommand("DEPT_CODE", "=", "APAC")),
|
||||
List.of()
|
||||
);
|
||||
@@ -67,6 +68,7 @@ class PermissionServiceTest {
|
||||
10L,
|
||||
1L,
|
||||
"SELECT",
|
||||
"ALLOW",
|
||||
List.of(new RuleCommand("DEPT_CODE", "CUSTOM_PREDICATE", "1=1")),
|
||||
List.of()
|
||||
);
|
||||
@@ -82,6 +84,7 @@ class PermissionServiceTest {
|
||||
10L,
|
||||
1L,
|
||||
"SELECT",
|
||||
"ALLOW",
|
||||
List.of(new RuleCommand(null, "MY_DEPT", null), new RuleCommand(null, "SELF", null)),
|
||||
List.of()
|
||||
);
|
||||
@@ -103,6 +106,7 @@ class PermissionServiceTest {
|
||||
10L,
|
||||
1L,
|
||||
"SELECT",
|
||||
"ALLOW",
|
||||
List.of(new RuleCommand(null, "DEPT", "HR"), new RuleCommand(null, "EMP_NO", "E2001")),
|
||||
List.of()
|
||||
);
|
||||
@@ -124,6 +128,7 @@ class PermissionServiceTest {
|
||||
10L,
|
||||
1L,
|
||||
"SELECT",
|
||||
"ALLOW",
|
||||
List.of(new RuleCommand(null, "DEPT", "")),
|
||||
List.of()
|
||||
);
|
||||
@@ -133,6 +138,23 @@ class PermissionServiceTest {
|
||||
.hasMessageContaining("값이 필요");
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsDenyPermissionEffect() {
|
||||
var command = new PermissionSetCommand(
|
||||
10L,
|
||||
1L,
|
||||
"SELECT",
|
||||
"DENY",
|
||||
List.of(new RuleCommand("DEPT_CODE", "=", "HR")),
|
||||
List.of()
|
||||
);
|
||||
|
||||
permissionService.savePermissionSet(command);
|
||||
|
||||
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
|
||||
assertThat(mapper.insertedEffect).isEqualTo("DENY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void disablesProtectedObjectWhenLastPermissionIsDeleted() {
|
||||
var mapper = new FakePermissionMapper();
|
||||
@@ -209,14 +231,22 @@ class PermissionServiceTest {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String insertedEffect;
|
||||
|
||||
@Override
|
||||
public void insertPermission(long permissionId, long roleId, long objectId, String action) {
|
||||
public void insertPermission(long permissionId, long roleId, long objectId, String action, String permissionEffect) {
|
||||
insertedEffect = permissionEffect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePermissionAction(long permissionId, String action) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePermissionEffect(long permissionId, String permissionEffect) {
|
||||
insertedEffect = permissionEffect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRules(long permissionId) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user