From 85d760a032d367905465151b84ce6b404f30f939 Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Wed, 3 Jun 2026 20:06:45 +0900 Subject: [PATCH] feat(admin): list a user's roles and groups (0.4.2) Adds GET /admin/api/v1/users/{id}/roles and /users/{id}/groups (returning [{value}] id arrays, backed by UserRoleService.findRoleIdsForUser / GroupMembershipService.findGroupsForUser) so the admin console can manage a user's access from the user side. Assign/revoke already exist on the role and group resources. Test: AdminRbacEnforcementTests hits both new endpoints (200 + JSON array). Full suite green: 55 tests, 0 failures/0 errors. Docs/CHANGELOG + install coordinates bumped to 0.4.2. --- CHANGELOG.ko.md | 9 +++++++ CHANGELOG.md | 9 +++++++ README.ko.md | 4 +-- README.md | 4 +-- .../kit/admin/user/UserAdminController.java | 26 ++++++++++++++++++- .../kit/sample/AdminRbacEnforcementTests.java | 26 +++++++++++++++++++ docs/getting-started/installation.ko.md | 12 ++++----- docs/getting-started/installation.md | 12 ++++----- docs/reference/configuration.ko.md | 4 +-- docs/reference/configuration.md | 4 +-- 10 files changed, 89 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.ko.md b/CHANGELOG.ko.md index c3bc66e..5bbc972 100644 --- a/CHANGELOG.ko.md +++ b/CHANGELOG.ko.md @@ -11,6 +11,15 @@ English: [CHANGELOG.md](CHANGELOG.md) ## [Unreleased] +## [0.4.2] — 2026-06-03 + +### Added +- **사용자의 역할·그룹 조회.** `GET /admin/api/v1/users/{id}/roles`, + `GET /admin/api/v1/users/{id}/groups` 가 사용자에게 부여된 역할/그룹 id 를 반환합니다. + 이로써 admin 콘솔이 **사용자 화면에서** 그 사용자의 접근권한을 관리할 수 있습니다(부여/회수는 + 기존대로 역할·그룹 리소스에: `POST/DELETE /roles/{roleId}/users/{userId}`, + `/groups/{groupId}/members/{userId}`). 안정적인 `[{ "value": "" }]` 배열로 반환. + ## [0.4.1] — 2026-06-03 ### Fixed diff --git a/CHANGELOG.md b/CHANGELOG.md index 117432b..f90a5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ The library major aligns with the Spring Boot major: `4.x.y` targets Spring Boot ## [Unreleased] +## [0.4.2] — 2026-06-03 + +### Added +- **List a user's roles and groups.** `GET /admin/api/v1/users/{id}/roles` and + `GET /admin/api/v1/users/{id}/groups` return the role / group ids assigned to a user, so the + admin console can manage a user's access from the user side (assign/revoke continue to live + on the role and group resources: `POST/DELETE /roles/{roleId}/users/{userId}` and + `/groups/{groupId}/members/{userId}`). Returned as a stable `[{ "value": "" }]` array. + ## [0.4.1] — 2026-06-03 ### Fixed diff --git a/README.ko.md b/README.ko.md index 7df6d45..3b93db4 100644 --- a/README.ko.md +++ b/README.ko.md @@ -63,7 +63,7 @@ **Gradle (Kotlin DSL)** ```kotlin -implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") +implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.2") ``` **Maven** @@ -72,7 +72,7 @@ implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") kr.devslab devslab-kit-spring-boot-starter - 0.4.1 + 0.4.2 ``` diff --git a/README.md b/README.md index aa0c237..ce9b847 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ specific product's domain. **Gradle (Kotlin DSL)** ```kotlin -implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") +implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.2") ``` **Maven** @@ -74,7 +74,7 @@ implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") kr.devslab devslab-kit-spring-boot-starter - 0.4.1 + 0.4.2 ``` diff --git a/devslab-kit-admin-api/src/main/java/kr/devslab/kit/admin/user/UserAdminController.java b/devslab-kit-admin-api/src/main/java/kr/devslab/kit/admin/user/UserAdminController.java index 9ebc8a1..971a288 100644 --- a/devslab-kit-admin-api/src/main/java/kr/devslab/kit/admin/user/UserAdminController.java +++ b/devslab-kit-admin-api/src/main/java/kr/devslab/kit/admin/user/UserAdminController.java @@ -3,7 +3,11 @@ import jakarta.validation.Valid; import java.util.List; import java.util.UUID; +import kr.devslab.kit.access.core.service.GroupMembershipService; +import kr.devslab.kit.access.core.service.UserRoleService; import kr.devslab.kit.admin.AdminApiPaths; +import kr.devslab.kit.core.id.GroupId; +import kr.devslab.kit.core.id.RoleId; import kr.devslab.kit.core.id.TenantId; import kr.devslab.kit.core.id.UserId; import kr.devslab.kit.identity.UserAccountView; @@ -26,13 +30,19 @@ public class UserAdminController { private final PlatformUserAccountAdminService adminService; private final PlatformUserAccountService readService; + private final UserRoleService userRoles; + private final GroupMembershipService groupMemberships; public UserAdminController( PlatformUserAccountAdminService adminService, - PlatformUserAccountService readService + PlatformUserAccountService readService, + UserRoleService userRoles, + GroupMembershipService groupMemberships ) { this.adminService = adminService; this.readService = readService; + this.userRoles = userRoles; + this.groupMemberships = groupMemberships; } @PostMapping @@ -59,6 +69,20 @@ public List list(@RequestParam String tenantId) { return adminService.listByTenant(TenantId.of(tenantId)); } + /** Roles assigned directly to this user. Assign/revoke live on the role resource + * ({@code POST/DELETE /roles/{roleId}/users/{userId}}). */ + @GetMapping("/{id}/roles") + public List roles(@PathVariable UUID id) { + return userRoles.findRoleIdsForUser(UserId.of(id)); + } + + /** Groups this user belongs to. Membership is managed on the group resource + * ({@code POST/DELETE /groups/{groupId}/members/{userId}}). */ + @GetMapping("/{id}/groups") + public List groups(@PathVariable UUID id) { + return groupMemberships.findGroupsForUser(UserId.of(id)); + } + @PutMapping("/{id}/lock") public ResponseEntity lock(@PathVariable UUID id) { adminService.lock(UserId.of(id)); diff --git a/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/AdminRbacEnforcementTests.java b/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/AdminRbacEnforcementTests.java index bb05b6f..78f3807 100644 --- a/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/AdminRbacEnforcementTests.java +++ b/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/AdminRbacEnforcementTests.java @@ -87,6 +87,32 @@ void authenticatedCallerWithoutThePermissionIsForbidden() throws Exception { assertThat(response.statusCode()).isEqualTo(403); } + @Test + void exposesAUsersRolesAndGroups() throws Exception { + String adminToken = login("admin", "admin"); + + // Resolve the seeded admin user's id from the user list. + HttpResponse users = getWithToken(USERS, adminToken); + assertThat(users.statusCode()).isEqualTo(200); + String adminId = null; + for (var node : objectMapper.readTree(users.body())) { + if ("admin".equals(node.get("loginId").asText())) { + adminId = node.get("id").get("value").asText(); + break; + } + } + assertThat(adminId).isNotNull(); + + // New endpoints: a user's roles and groups, returned as a stable [{value}] array. + HttpResponse roles = getWithToken("/admin/api/v1/users/" + adminId + "/roles", adminToken); + assertThat(roles.statusCode()).isEqualTo(200); + assertThat(objectMapper.readTree(roles.body()).isArray()).isTrue(); + + HttpResponse groups = getWithToken("/admin/api/v1/users/" + adminId + "/groups", adminToken); + assertThat(groups.statusCode()).isEqualTo(200); + assertThat(objectMapper.readTree(groups.body()).isArray()).isTrue(); + } + private String login(String loginId, String password) throws Exception { HttpResponse response = post(LOGIN, loginBody(loginId, password)); assertThat(response.statusCode()).isEqualTo(200); diff --git a/docs/getting-started/installation.ko.md b/docs/getting-started/installation.ko.md index 6d818aa..87f04e8 100644 --- a/docs/getting-started/installation.ko.md +++ b/docs/getting-started/installation.ko.md @@ -17,13 +17,13 @@ === "Gradle (Kotlin DSL)" ```kotlin - implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") + implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.2") ``` === "Gradle (Groovy)" ```groovy - implementation 'kr.devslab:devslab-kit-spring-boot-starter:0.4.1' + implementation 'kr.devslab:devslab-kit-spring-boot-starter:0.4.2' ``` === "Maven" @@ -32,7 +32,7 @@ kr.devslab devslab-kit-spring-boot-starter - 0.4.1 + 0.4.2 ``` @@ -43,10 +43,10 @@ 물러납니다(`@ConditionalOnMissingBean`). ```kotlin -implementation("kr.devslab:devslab-kit-access-core:0.4.1") // RBAC + 그룹 + ABAC -implementation("kr.devslab:devslab-kit-cache-core:0.4.1") // 플러그형 캐시 +implementation("kr.devslab:devslab-kit-access-core:0.4.2") // RBAC + 그룹 + ABAC +implementation("kr.devslab:devslab-kit-cache-core:0.4.2") // 플러그형 캐시 // …또는 계약만: -implementation("kr.devslab:devslab-kit-access-api:0.4.1") +implementation("kr.devslab:devslab-kit-access-api:0.4.2") ``` 동작하는 앱을 부팅하려면 [빠른 시작](quick-start.md)을 참고하세요. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 634eb52..718cedd 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -17,13 +17,13 @@ whole platform; depend on individual modules only if you want à la carte. === "Gradle (Kotlin DSL)" ```kotlin - implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") + implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.2") ``` === "Gradle (Groovy)" ```groovy - implementation 'kr.devslab:devslab-kit-spring-boot-starter:0.4.1' + implementation 'kr.devslab:devslab-kit-spring-boot-starter:0.4.2' ``` === "Maven" @@ -32,7 +32,7 @@ whole platform; depend on individual modules only if you want à la carte. kr.devslab devslab-kit-spring-boot-starter - 0.4.1 + 0.4.2 ``` @@ -44,10 +44,10 @@ your own — the auto-configuration backs off (`@ConditionalOnMissingBean`) when do. ```kotlin -implementation("kr.devslab:devslab-kit-access-core:0.4.1") // RBAC + groups + ABAC -implementation("kr.devslab:devslab-kit-cache-core:0.4.1") // pluggable cache +implementation("kr.devslab:devslab-kit-access-core:0.4.2") // RBAC + groups + ABAC +implementation("kr.devslab:devslab-kit-cache-core:0.4.2") // pluggable cache // …or just the contract: -implementation("kr.devslab:devslab-kit-access-api:0.4.1") +implementation("kr.devslab:devslab-kit-access-api:0.4.2") ``` See [Quick Start](quick-start.md) to boot a working app. diff --git a/docs/reference/configuration.ko.md b/docs/reference/configuration.ko.md index 7b91d6f..a9a9ed5 100644 --- a/docs/reference/configuration.ko.md +++ b/docs/reference/configuration.ko.md @@ -121,7 +121,7 @@ === "Gradle (Kotlin DSL)" ```kotlin - implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") { + implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.2") { exclude(group = "org.springdoc") } ``` @@ -132,7 +132,7 @@ kr.devslab devslab-kit-spring-boot-starter - 0.4.1 + 0.4.2 org.springdoc diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 8056cc7..4d9489d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -128,7 +128,7 @@ Two ways to turn it off: === "Gradle (Kotlin DSL)" ```kotlin - implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.1") { + implementation("kr.devslab:devslab-kit-spring-boot-starter:0.4.2") { exclude(group = "org.springdoc") } ``` @@ -139,7 +139,7 @@ Two ways to turn it off: kr.devslab devslab-kit-spring-boot-starter - 0.4.1 + 0.4.2 org.springdoc