Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Build

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
name: Gradle build (Java 21, Testcontainers)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Java 21 (GraalVM CE)
uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm-community'
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Gradle (wrapper)
uses: gradle/actions/setup-gradle@v4

- name: Build (compile + test)
run: ./gradlew build --no-daemon --stacktrace

- name: Upload test reports on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: |
**/build/reports/tests/
**/build/test-results/
if-no-files-found: ignore
retention-days: 7
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ subprojects {
"testRuntimeOnly"("org.junit.platform:junit-platform-launcher")
}

tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-parameters")
}

tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.devslab.kit.access;

import java.util.Objects;
import java.util.Optional;
import kr.devslab.kit.core.id.GroupId;
import kr.devslab.kit.core.id.TenantId;

public record Group(
GroupId id,
TenantId tenantId,
String code,
String name,
Optional<GroupId> parentGroupId
) {

public Group {
Objects.requireNonNull(id, "Group id must not be null");
Objects.requireNonNull(tenantId, "Group tenantId must not be null");
if (code == null || code.isBlank()) {
throw new IllegalArgumentException("Group code must not be null or blank");
}
parentGroupId = parentGroupId == null ? Optional.empty() : parentGroupId;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kr.devslab.kit.access;

import kr.devslab.kit.access.policy.PolicyContext;

public interface PermissionChecker {

boolean hasPermission(Permission permission);
Expand All @@ -8,9 +10,19 @@ public interface PermissionChecker {

boolean hasAllPermissions(Permission... permissions);

default boolean isAllowed(Permission permission, String policyName, PolicyContext context) {
return hasPermission(permission);
}

default void check(Permission permission) {
if (!hasPermission(permission)) {
throw new PermissionDeniedException(permission);
}
}

default void check(Permission permission, String policyName, PolicyContext context) {
if (!isAllowed(permission, policyName, context)) {
throw new PermissionDeniedException(permission);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.devslab.kit.access.policy;

public interface Policy {

String name();

PolicyDecision evaluate(PolicyContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kr.devslab.kit.access.policy;

import java.util.Map;
import java.util.Optional;
import kr.devslab.kit.core.id.TenantId;
import kr.devslab.kit.core.id.UserId;

public record PolicyContext(
Optional<UserId> userId,
Optional<TenantId> tenantId,
String resourceType,
String resourceId,
Map<String, Object> resourceAttributes,
Map<String, Object> environmentAttributes
) {

public PolicyContext {
userId = userId == null ? Optional.empty() : userId;
tenantId = tenantId == null ? Optional.empty() : tenantId;
resourceAttributes = resourceAttributes == null ? Map.of() : Map.copyOf(resourceAttributes);
environmentAttributes = environmentAttributes == null ? Map.of() : Map.copyOf(environmentAttributes);
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {
private Optional<UserId> userId = Optional.empty();
private Optional<TenantId> tenantId = Optional.empty();
private String resourceType;
private String resourceId;
private Map<String, Object> resourceAttributes = Map.of();
private Map<String, Object> environmentAttributes = Map.of();

public Builder user(UserId id) { this.userId = Optional.ofNullable(id); return this; }
public Builder tenant(TenantId id) { this.tenantId = Optional.ofNullable(id); return this; }
public Builder resource(String type, String id) { this.resourceType = type; this.resourceId = id; return this; }
public Builder resourceAttributes(Map<String, Object> attrs) { this.resourceAttributes = attrs; return this; }
public Builder environmentAttributes(Map<String, Object> attrs) { this.environmentAttributes = attrs; return this; }
public PolicyContext build() {
return new PolicyContext(userId, tenantId, resourceType, resourceId, resourceAttributes, environmentAttributes);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.devslab.kit.access.policy;

public enum PolicyDecision {
ALLOW,
DENY,
NOT_APPLICABLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kr.devslab.kit.access.policy;

public interface PolicyEvaluator {

PolicyDecision evaluate(String policyName, PolicyContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package kr.devslab.kit.access.core.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.Instant;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(
name = "platform_group",
uniqueConstraints = @UniqueConstraint(name = "uq_platform_group_tenant_code", columnNames = {"tenant_id", "code"})
)
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PlatformGroupEntity {

@Id
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
private UUID id;

@Column(name = "tenant_id", nullable = false, length = 64)
private String tenantId;

@Column(name = "code", nullable = false, length = 64)
private String code;

@Column(name = "name", nullable = false, length = 128)
private String name;

@Column(name = "parent_group_id", columnDefinition = "uuid")
private UUID parentGroupId;

@Column(name = "created_at", nullable = false)
private Instant createdAt;

public PlatformGroupEntity(UUID id, String tenantId, String code, String name, UUID parentGroupId, Instant createdAt) {
this.id = id;
this.tenantId = tenantId;
this.code = code;
this.name = name;
this.parentGroupId = parentGroupId;
this.createdAt = createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package kr.devslab.kit.access.core.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.Instant;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(
name = "platform_group_role",
uniqueConstraints = @UniqueConstraint(name = "uq_platform_group_role_group_role", columnNames = {"group_id", "role_id"})
)
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PlatformGroupRoleEntity {

@Id
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
private UUID id;

@Column(name = "group_id", columnDefinition = "uuid", nullable = false)
private UUID groupId;

@Column(name = "role_id", columnDefinition = "uuid", nullable = false)
private UUID roleId;

@Column(name = "assigned_at", nullable = false)
private Instant assignedAt;

public PlatformGroupRoleEntity(UUID id, UUID groupId, UUID roleId, Instant assignedAt) {
this.id = id;
this.groupId = groupId;
this.roleId = roleId;
this.assignedAt = assignedAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package kr.devslab.kit.access.core.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.Instant;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(
name = "platform_user_group",
uniqueConstraints = @UniqueConstraint(name = "uq_platform_user_group_user_group", columnNames = {"user_id", "group_id"})
)
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PlatformUserGroupEntity {

@Id
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
private UUID id;

@Column(name = "user_id", columnDefinition = "uuid", nullable = false)
private UUID userId;

@Column(name = "group_id", columnDefinition = "uuid", nullable = false)
private UUID groupId;

@Column(name = "joined_at", nullable = false)
private Instant joinedAt;

public PlatformUserGroupEntity(UUID id, UUID userId, UUID groupId, Instant joinedAt) {
this.id = id;
this.userId = userId;
this.groupId = groupId;
this.joinedAt = joinedAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kr.devslab.kit.access.core.repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import kr.devslab.kit.access.core.entity.PlatformGroupEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaPlatformGroupRepository extends JpaRepository<PlatformGroupEntity, UUID> {

Optional<PlatformGroupEntity> findByTenantIdAndCode(String tenantId, String code);

List<PlatformGroupEntity> findAllByTenantId(String tenantId);

List<PlatformGroupEntity> findAllByParentGroupId(UUID parentGroupId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kr.devslab.kit.access.core.repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import kr.devslab.kit.access.core.entity.PlatformGroupRoleEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaPlatformGroupRoleRepository extends JpaRepository<PlatformGroupRoleEntity, UUID> {

List<PlatformGroupRoleEntity> findAllByGroupId(UUID groupId);

Optional<PlatformGroupRoleEntity> findByGroupIdAndRoleId(UUID groupId, UUID roleId);

void deleteByGroupIdAndRoleId(UUID groupId, UUID roleId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ public interface JpaPlatformPermissionRepository extends JpaRepository<PlatformP
@Query(value = """
SELECT DISTINCT p.code FROM platform_permission p
JOIN platform_role_permission rp ON rp.permission_id = p.id
JOIN platform_user_role ur ON ur.role_id = rp.role_id
WHERE ur.user_id = :userId
WHERE rp.role_id IN (
SELECT ur.role_id FROM platform_user_role ur WHERE ur.user_id = :userId
UNION
SELECT gr.role_id FROM platform_group_role gr
JOIN platform_user_group ug ON ug.group_id = gr.group_id
WHERE ug.user_id = :userId
)
""", nativeQuery = true)
Set<String> findCodesForUserId(@Param("userId") UUID userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package kr.devslab.kit.access.core.repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import kr.devslab.kit.access.core.entity.PlatformUserGroupEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaPlatformUserGroupRepository extends JpaRepository<PlatformUserGroupEntity, UUID> {

List<PlatformUserGroupEntity> findAllByUserId(UUID userId);

List<PlatformUserGroupEntity> findAllByGroupId(UUID groupId);

Optional<PlatformUserGroupEntity> findByUserIdAndGroupId(UUID userId, UUID groupId);

void deleteByUserIdAndGroupId(UUID userId, UUID groupId);
}
Loading
Loading