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 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 15 | **Repo** | https://github.com/reporkey/vibe-erp |
16 16 | **Modules** | 18 |
17 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 19 import org.vibeerp.pbc.identity.application.UpdateUserCommand
20 20 import org.vibeerp.pbc.identity.application.UserService
21 21 import org.vibeerp.pbc.identity.domain.User
  22 +import org.vibeerp.platform.security.authz.RequirePermission
22 23 import java.util.UUID
23 24  
24 25 /**
... ... @@ -34,8 +35,13 @@ import java.util.UUID
34 35 * filtering and exposes no `tenantId` parameter. Customer isolation happens
35 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 46 @RestController
41 47 @RequestMapping("/api/v1/identity/users")
... ... @@ -44,10 +50,12 @@ class UserController(
44 50 ) {
45 51  
46 52 @GetMapping
  53 + @RequirePermission("identity.user.read")
47 54 fun list(): List<UserResponse> =
48 55 userService.list().map { it.toResponse() }
49 56  
50 57 @GetMapping("/{id}")
  58 + @RequirePermission("identity.user.read")
51 59 fun get(@PathVariable id: UUID): ResponseEntity<UserResponse> {
52 60 val user = userService.findById(id) ?: return ResponseEntity.notFound().build()
53 61 return ResponseEntity.ok(user.toResponse())
... ... @@ -55,6 +63,7 @@ class UserController(
55 63  
56 64 @PostMapping
57 65 @ResponseStatus(HttpStatus.CREATED)
  66 + @RequirePermission("identity.user.create")
58 67 fun create(@RequestBody @Valid request: CreateUserRequest): UserResponse =
59 68 userService.create(
60 69 CreateUserCommand(
... ... @@ -66,6 +75,7 @@ class UserController(
66 75 ).toResponse()
67 76  
68 77 @PatchMapping("/{id}")
  78 + @RequirePermission("identity.user.update")
69 79 fun update(
70 80 @PathVariable id: UUID,
71 81 @RequestBody @Valid request: UpdateUserRequest,
... ... @@ -81,6 +91,7 @@ class UserController(
81 91  
82 92 @DeleteMapping("/{id}")
83 93 @ResponseStatus(HttpStatus.NO_CONTENT)
  94 + @RequirePermission("identity.user.disable")
84 95 fun disable(@PathVariable id: UUID) {
85 96 userService.disable(id)
86 97 }
... ...