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`.
|
||||||
49
pom.xml
49
pom.xml
|
|
@ -3,37 +3,54 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
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">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>com.klaroworks</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>developer-role-smoke</artifactId>
|
||||||
<version>3.2.5</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
<relativePath/>
|
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>developer-role-smoke</artifactId>
|
<artifactId>developer-role-smoke</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
<name>Developer Role Smoke Test</name>
|
<packaging>jar</packaging>
|
||||||
<description>Spring Boot smoke application for Developer role</description>
|
|
||||||
|
<name>Developer Role Smoke</name>
|
||||||
|
<description>Runtime role matrix for developer role validation</description>
|
||||||
|
|
||||||
<properties>
|
<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>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<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>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
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