Commit f1842f5a07d4f776b29b5ae89092228f8a0ab6b9
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.
Showing
2 changed files
with
15 additions
and
4 deletions
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 | } | ... | ... |