Commit f1842f5a07d4f776b29b5ae89092228f8a0ab6b9

Authored by zichun
1 parent 4c70fd54

feat(security): annotate pbc-identity user endpoints with @RequirePermission

Closes the P4.3 rollout — the last PBC whose controllers were still
unannotated. Every endpoint in `UserController` now carries an
`@RequirePermission("identity.user.*")` annotation matching the keys
already declared in `identity.yml`:

  GET  /api/v1/identity/users           identity.user.read
  GET  /api/v1/identity/users/{id}      identity.user.read
  POST /api/v1/identity/users           identity.user.create
  PATCH /api/v1/identity/users/{id}     identity.user.update
  DELETE /api/v1/identity/users/{id}    identity.user.disable

`AuthController` (login, refresh) is deliberately NOT annotated —
it is in the platform-security public allowlist because login is the
token-issuing endpoint (chicken-and-egg).

KDoc on the controller class updated to reflect the new auth story
(removing the stale "authentication deferred to v0.2" comment from
before P4.1 / P4.3 landed).

Smoke verified end-to-end against real Postgres:
  - Admin (wildcard `admin` role) → GET /users returns 200, POST
    /users returns 201 (new user `jane` created).
  - Unauthenticated GET and POST → 401 Unauthorized from the
    framework's JWT filter before @RequirePermission runs. A
    non-admin user without explicit grants would get 403 from the
    AOP evaluator; tested manually with the admin and anonymous
    cases.

No test changes — the controller unit test is a thin DTO mapper
test that doesn't exercise the Spring AOP aspect; identity-wide
authz enforcement is covered by the platform-security tests plus
the shipping smoke tests. 246 unit tests, all green.

P4.3 is now complete across every core PBC:
  pbc-catalog, pbc-partners, pbc-inventory, pbc-orders-sales,
  pbc-orders-purchase, pbc-finance, pbc-production, pbc-identity.
PROGRESS.md
@@ -10,8 +10,8 @@ @@ -10,8 +10,8 @@
10 10
11 | | | 11 | | |
12 |---|---| 12 |---|---|
13 -| **Latest version** | v0.19.1 (HasExt marker interface + ExtJsonValidator helpers) |  
14 -| **Latest commit** | `986f02c refactor(metadata): HasExt marker + applyTo/parseExt helpers on ExtJsonValidator` | 13 +| **Latest version** | v0.19.2 (pbc-identity @RequirePermission rollout — P4.3 complete) |
  14 +| **Latest commit** | `TBD feat(security): annotate pbc-identity user endpoints with @RequirePermission` |
15 | **Repo** | https://github.com/reporkey/vibe-erp | 15 | **Repo** | https://github.com/reporkey/vibe-erp |
16 | **Modules** | 18 | 16 | **Modules** | 18 |
17 | **Unit tests** | 246, all green | 17 | **Unit tests** | 246, all green |
pbc/pbc-identity/src/main/kotlin/org/vibeerp/pbc/identity/http/UserController.kt
@@ -19,6 +19,7 @@ import org.vibeerp.pbc.identity.application.CreateUserCommand @@ -19,6 +19,7 @@ import org.vibeerp.pbc.identity.application.CreateUserCommand
19 import org.vibeerp.pbc.identity.application.UpdateUserCommand 19 import org.vibeerp.pbc.identity.application.UpdateUserCommand
20 import org.vibeerp.pbc.identity.application.UserService 20 import org.vibeerp.pbc.identity.application.UserService
21 import org.vibeerp.pbc.identity.domain.User 21 import org.vibeerp.pbc.identity.domain.User
  22 +import org.vibeerp.platform.security.authz.RequirePermission
22 import java.util.UUID 23 import java.util.UUID
23 24
24 /** 25 /**
@@ -34,8 +35,13 @@ import java.util.UUID @@ -34,8 +35,13 @@ import java.util.UUID
34 * filtering and exposes no `tenantId` parameter. Customer isolation happens 35 * filtering and exposes no `tenantId` parameter. Customer isolation happens
35 * at the deployment level (one running instance per customer). 36 * at the deployment level (one running instance per customer).
36 * 37 *
37 - * Authentication is deferred to v0.2 (pbc-identity's auth flow). For v0.1  
38 - * the controller answers any request. 38 + * **Authorization.** Every endpoint is annotated with `@RequirePermission`.
  39 + * The identity-PBC permission keys are declared in
  40 + * `META-INF/vibe-erp/metadata/identity.yml` and enforced by the same
  41 + * `PermissionEvaluator` AOP aspect that covers every other PBC. Login /
  42 + * refresh endpoints are deliberately NOT here — they live on
  43 + * [AuthController] and are public (they can't require a token to issue
  44 + * a token).
39 */ 45 */
40 @RestController 46 @RestController
41 @RequestMapping("/api/v1/identity/users") 47 @RequestMapping("/api/v1/identity/users")
@@ -44,10 +50,12 @@ class UserController( @@ -44,10 +50,12 @@ class UserController(
44 ) { 50 ) {
45 51
46 @GetMapping 52 @GetMapping
  53 + @RequirePermission("identity.user.read")
47 fun list(): List<UserResponse> = 54 fun list(): List<UserResponse> =
48 userService.list().map { it.toResponse() } 55 userService.list().map { it.toResponse() }
49 56
50 @GetMapping("/{id}") 57 @GetMapping("/{id}")
  58 + @RequirePermission("identity.user.read")
51 fun get(@PathVariable id: UUID): ResponseEntity<UserResponse> { 59 fun get(@PathVariable id: UUID): ResponseEntity<UserResponse> {
52 val user = userService.findById(id) ?: return ResponseEntity.notFound().build() 60 val user = userService.findById(id) ?: return ResponseEntity.notFound().build()
53 return ResponseEntity.ok(user.toResponse()) 61 return ResponseEntity.ok(user.toResponse())
@@ -55,6 +63,7 @@ class UserController( @@ -55,6 +63,7 @@ class UserController(
55 63
56 @PostMapping 64 @PostMapping
57 @ResponseStatus(HttpStatus.CREATED) 65 @ResponseStatus(HttpStatus.CREATED)
  66 + @RequirePermission("identity.user.create")
58 fun create(@RequestBody @Valid request: CreateUserRequest): UserResponse = 67 fun create(@RequestBody @Valid request: CreateUserRequest): UserResponse =
59 userService.create( 68 userService.create(
60 CreateUserCommand( 69 CreateUserCommand(
@@ -66,6 +75,7 @@ class UserController( @@ -66,6 +75,7 @@ class UserController(
66 ).toResponse() 75 ).toResponse()
67 76
68 @PatchMapping("/{id}") 77 @PatchMapping("/{id}")
  78 + @RequirePermission("identity.user.update")
69 fun update( 79 fun update(
70 @PathVariable id: UUID, 80 @PathVariable id: UUID,
71 @RequestBody @Valid request: UpdateUserRequest, 81 @RequestBody @Valid request: UpdateUserRequest,
@@ -81,6 +91,7 @@ class UserController( @@ -81,6 +91,7 @@ class UserController(
81 91
82 @DeleteMapping("/{id}") 92 @DeleteMapping("/{id}")
83 @ResponseStatus(HttpStatus.NO_CONTENT) 93 @ResponseStatus(HttpStatus.NO_CONTENT)
  94 + @RequirePermission("identity.user.disable")
84 fun disable(@PathVariable id: UUID) { 95 fun disable(@PathVariable id: UUID) {
85 userService.disable(id) 96 userService.disable(id)
86 } 97 }