Reviewer 역할 검증 보고서 smoke #9
5 changed files with 321 additions and 16 deletions
|
|
@ -0,0 +1,3 @@
|
|||
# runtime-role-matrix-live-20260714101723-v7-reviewer-001-attempt-3-run-f5cb07374744
|
||||
|
||||
Forge 이슈 작업 브랜치 `forge/runtime-role-matrix-live-20260714101723-v7-reviewer-001-attempt-3-run-f5cb07374744`.
|
||||
47
pom.xml
47
pom.xml
|
|
@ -3,36 +3,53 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<relativePath/>
|
||||
<groupId>com.klaroworks</groupId>
|
||||
<artifactId>developer-role-smoke</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
|
||||
<artifactId>developer-role-smoke</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>Developer Role Smoke Test</name>
|
||||
<description>Spring Boot smoke application for Developer role</description>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Developer Role Smoke</name>
|
||||
<description>Runtime role matrix for developer role validation</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.version>5.10.0</junit.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
|
|
|||
59
src/main/java/com/klaroworks/runtime/role/RoleContext.java
Normal file
59
src/main/java/com/klaroworks/runtime/role/RoleContext.java
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package com.klaroworks.runtime.role;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Context holder for role evaluation containing user identity and runtime attributes.
|
||||
*/
|
||||
public final class RoleContext {
|
||||
|
||||
private final String userId;
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
public RoleContext(String userId) {
|
||||
this(userId, new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
public RoleContext(String userId, Map<String, Object> attributes) {
|
||||
this.userId = Objects.requireNonNull(userId, "userId must not be null");
|
||||
this.attributes = new ConcurrentHashMap<>(Objects.requireNonNull(attributes, "attributes must not be null"));
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
return Map.copyOf(attributes);
|
||||
}
|
||||
|
||||
public Object getAttribute(String key) {
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
public RoleContext withAttribute(String key, Object value) {
|
||||
Map<String, Object> newAttrs = new ConcurrentHashMap<>(attributes);
|
||||
newAttrs.put(key, value);
|
||||
return new RoleContext(userId, newAttrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RoleContext that = (RoleContext) o;
|
||||
return Objects.equals(userId, that.userId) && Objects.equals(attributes, that.attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(userId, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RoleContext{userId='" + userId + "', attributes=" + attributes + "}";
|
||||
}
|
||||
}
|
||||
132
src/main/java/com/klaroworks/runtime/role/RoleMatrixRuntime.java
Normal file
132
src/main/java/com/klaroworks/runtime/role/RoleMatrixRuntime.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package com.klaroworks.runtime.role;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Runtime engine for evaluating role-based access and permissions.
|
||||
*/
|
||||
public final class RoleMatrixRuntime {
|
||||
|
||||
private final Map<String, Set<String>> roleHierarchy;
|
||||
private final Map<String, Set<String>> rolePermissions;
|
||||
private final RoleValidator validator;
|
||||
private final ThreadLocal<RoleContext> currentContext;
|
||||
|
||||
public RoleMatrixRuntime() {
|
||||
this(new HashMap<>(), new HashMap<>(), new RoleValidator());
|
||||
}
|
||||
|
||||
public RoleMatrixRuntime(Map<String, Set<String>> roleHierarchy, Map<String, Set<String>> rolePermissions, RoleValidator validator) {
|
||||
this.roleHierarchy = new ConcurrentHashMap<>(roleHierarchy);
|
||||
this.rolePermissions = new ConcurrentHashMap<>(rolePermissions);
|
||||
this.validator = validator != null ? validator : new RoleValidator();
|
||||
this.currentContext = ThreadLocal.withInitial(() -> null);
|
||||
}
|
||||
|
||||
public boolean hasRole(RoleContext context, String role) {
|
||||
if (context == null || role == null) {
|
||||
return false;
|
||||
}
|
||||
Set<String> userRoles = getUserRoles(context);
|
||||
return userRoles.contains(role) || hasRoleInHierarchy(role, userRoles);
|
||||
}
|
||||
|
||||
public boolean hasPermission(RoleContext context, String permission) {
|
||||
if (context == null || permission == null) {
|
||||
return false;
|
||||
}
|
||||
Set<String> userRoles = getUserRoles(context);
|
||||
for (String role : userRoles) {
|
||||
Set<String> perms = rolePermissions.get(role);
|
||||
if (perms != null && perms.contains(permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean evaluateRole(RoleContext context, String roleExpression) {
|
||||
if (context == null || roleExpression == null) {
|
||||
return false;
|
||||
}
|
||||
String[] parts = roleExpression.split("\\|");
|
||||
for (String part : parts) {
|
||||
String trimmed = part.trim();
|
||||
if (trimmed.startsWith("!") && hasRole(context, trimmed.substring(1))) {
|
||||
return false;
|
||||
} else if (hasRole(context, trimmed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setContext(RoleContext context) {
|
||||
currentContext.set(context);
|
||||
}
|
||||
|
||||
public RoleContext getContext() {
|
||||
return currentContext.get();
|
||||
}
|
||||
|
||||
public void clearContext() {
|
||||
currentContext.remove();
|
||||
}
|
||||
|
||||
public void defineRole(String role, Set<String> parentRoles) {
|
||||
roleHierarchy.put(role, Set.copyOf(parentRoles));
|
||||
}
|
||||
|
||||
public void assignPermissions(String role, Set<String> permissions) {
|
||||
rolePermissions.put(role, Set.copyOf(permissions));
|
||||
}
|
||||
|
||||
private Set<String> getUserRoles(RoleContext context) {
|
||||
Object rolesObj = context.getAttribute("roles");
|
||||
if (rolesObj instanceof Collection) {
|
||||
Set<String> roles = new HashSet<>();
|
||||
for (Object r : (Collection<?>) rolesObj) {
|
||||
if (r != null) {
|
||||
roles.add(r.toString());
|
||||
}
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private boolean hasRoleInHierarchy(String targetRole, Set<String> userRoles) {
|
||||
for (String userRole : userRoles) {
|
||||
if (hasInHierarchy(targetRole, userRole, new HashSet<>())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasInHierarchy(String target, String current, Set<String> visited) {
|
||||
if (visited.contains(current)) {
|
||||
return false;
|
||||
}
|
||||
visited.add(current);
|
||||
Set<String> parents = roleHierarchy.get(current);
|
||||
if (parents != null) {
|
||||
if (parents.contains(target)) {
|
||||
return true;
|
||||
}
|
||||
for (String parent : parents) {
|
||||
if (hasInHierarchy(target, parent, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final class Nested {
|
||||
public static boolean evaluate(RoleMatrixRuntime runtime, RoleContext context, String expression) {
|
||||
return runtime.evaluateRole(context, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/main/java/com/klaroworks/runtime/role/RoleValidator.java
Normal file
94
src/main/java/com/klaroworks/runtime/role/RoleValidator.java
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package com.klaroworks.runtime.role;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Validates role context and permission requests against configured rules.
|
||||
*/
|
||||
public final class RoleValidator {
|
||||
|
||||
private final Set<String> validRoles;
|
||||
private final Set<String> validPermissions;
|
||||
|
||||
public RoleValidator() {
|
||||
this(Collections.emptySet(), Collections.emptySet());
|
||||
}
|
||||
|
||||
public RoleValidator(Collection<String> validRoles, Collection<String> validPermissions) {
|
||||
this.validRoles = Set.copyOf(validRoles);
|
||||
this.validPermissions = Set.copyOf(validPermissions);
|
||||
}
|
||||
|
||||
public boolean isValidRole(String role) {
|
||||
if (role == null || role.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return validRoles.isEmpty() || validRoles.contains(role);
|
||||
}
|
||||
|
||||
public boolean isValidPermission(String permission) {
|
||||
if (permission == null || permission.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return validPermissions.isEmpty() || validPermissions.contains(permission);
|
||||
}
|
||||
|
||||
public ValidationResult validateContext(RoleContext context) {
|
||||
if (context == null) {
|
||||
return new ValidationResult(false, "Context must not be null");
|
||||
}
|
||||
if (context.getUserId() == null || context.getUserId().isBlank()) {
|
||||
return new ValidationResult(false, "User ID must not be blank");
|
||||
}
|
||||
return new ValidationResult(true, null);
|
||||
}
|
||||
|
||||
public ValidationResult validateRole(String role) {
|
||||
if (role == null || role.isBlank()) {
|
||||
return new ValidationResult(false, "Role must not be blank");
|
||||
}
|
||||
if (!validRoles.isEmpty() && !validRoles.contains(role)) {
|
||||
return new ValidationResult(false, "Unknown role: " + role);
|
||||
}
|
||||
return new ValidationResult(true, null);
|
||||
}
|
||||
|
||||
public ValidationResult validatePermission(String permission) {
|
||||
if (permission == null || permission.isBlank()) {
|
||||
return new ValidationResult(false, "Permission must not be blank");
|
||||
}
|
||||
if (!validPermissions.isEmpty() && !validPermissions.contains(permission)) {
|
||||
return new ValidationResult(false, "Unknown permission: " + permission);
|
||||
}
|
||||
return new ValidationResult(true, null);
|
||||
}
|
||||
|
||||
public Set<String> getValidRoles() {
|
||||
return Collections.unmodifiableSet(validRoles);
|
||||
}
|
||||
|
||||
public Set<String> getValidPermissions() {
|
||||
return Collections.unmodifiableSet(validPermissions);
|
||||
}
|
||||
|
||||
public static final class ValidationResult {
|
||||
private final boolean valid;
|
||||
private final String errorMessage;
|
||||
|
||||
public ValidationResult(boolean valid, String errorMessage) {
|
||||
this.valid = valid;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue