Skip to content

Enhancement/refactor security in employment - #65

Merged
codebyNorthsteep merged 10 commits into
mainfrom
enhancement/refactorSecurityInEmployment
Apr 15, 2026
Merged

Enhancement/refactor security in employment#65
codebyNorthsteep merged 10 commits into
mainfrom
enhancement/refactorSecurityInEmployment

Conversation

@codebyNorthsteep

@codebyNorthsteep codebyNorthsteep commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

This pull request refactors the employment form feature to improve exception handling, centralize and clarify authorization logic, and simplify the controller endpoints. It introduces a custom exception for missing employment forms, consolidates filtering logic for forms, and moves security annotations from the controller to the service layer for better separation of concerns. Additionally, it removes outdated or redundant controller endpoints and updates test coverage accordingly.

Exception handling improvements:

  • Added a custom EmploymentFormNotFound exception and updated the service to throw it when a form is not found, replacing the generic EntityNotFoundException. The global exception handler now returns a 404 response for this case. [1] [2] [3] [4]
  • The service now consistently throws StaffNotFoundException for missing staff, improving error clarity. [1] [2] [3] [4]

Authorization and security changes:

  • Moved all @PreAuthorize annotations from the controller to the service layer, enforcing role-based access control closer to the business logic. [1] [2] [3] [4] [5] [6] [7] [8] [9]
  • Updated method-level security to clarify which roles can perform create, update, approve, reject, and delete actions on employment forms. [1] [2] [3] [4] [5]

Controller and API simplification:

  • Replaced separate endpoints for pending and approved forms with a unified getForms endpoint that supports filtering by approval status. [1] [2]
  • Removed redundant or outdated endpoints and simplified method signatures in EmploymentFormController. [1] [2] [3] [4]

Testing updates:

  • Removed the controller test class EmploymentFormControllerTests.java, likely due to changes in endpoint structure or security configuration.
  • Updated service layer tests to reflect role requirements (e.g., using CTO instead of a generic manager for rejection).

These changes collectively make the employment form feature more robust, maintainable, and secure.

Summary by CodeRabbit

  • Bug Fixes

    • Improved API error responses with specific HTTP statuses and JSON error messages for missing forms (404), invalid input (400), and access denied (403).
  • New Features

    • Single forms retrieval endpoint added with optional status filter.
  • Changes

    • Endpoint authorization behavior reorganized to enforce role-based checks differently across the application.
  • Tests

    • Added security integration tests and removed older controller-level tests; test profile and test properties updated.

…ole-based access from `EmploymentFormController` to service layer.
…olidate approval status filtering logic into a single endpoint, enhance security with `@PreAuthorize` in service layer.
…nnotations for role-based access control in service layer. Clean up redundant controller-level security annotations.
…loymentFormNotFound` and `StaffNotFoundException`, update exception types, and enhance `GlobalExceptionHandler` with custom responses for new exceptions.
…lerTests` with `SecurityIntegrationTests`, update `EmploymentFormService` to use `StaffNotFoundException` instead of `EntityNotFoundException`, and adjust related service tests for role-specific logic.
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Removed controller-level role checks and consolidated list endpoints; added domain exception EmploymentFormNotFound; moved authorization to service methods with @PreAuthorize; extended global exception handler to map EmploymentFormNotFound, IllegalArgumentException, and Spring Security AccessDeniedException to HTTP responses; tests updated/added accordingly.

Changes

Cohort / File(s) Summary
Exception Handling
src/main/java/org/example/cyberwatch/exception/GlobalExceptionHandler.java, src/main/java/org/example/cyberwatch/features/form/exception/EmploymentFormNotFound.java
Added EmploymentFormNotFound exception class and three new handlers: 404 for form-not-found, 400 for IllegalArgumentException, and 403 for AccessDeniedException.
Controller API & Authorization
src/main/java/org/example/cyberwatch/features/form/controller/EmploymentFormController.java
Removed method-level @PreAuthorize annotations from controller methods; merged /pending and /approved into a single GET with optional ApprovalStatus request param; normalized @PostMapping syntax.
Service Layer Security & Logic
src/main/java/org/example/cyberwatch/features/form/service/EmploymentFormService.java
Added @PreAuthorize on service methods with role-specific rules; replaced status-specific getters with getFormsByFilterApproval(ApprovalStatus) (supports null for all); switched thrown exceptions to domain-specific (StaffNotFoundException, EmploymentFormNotFound).
Tests — removal, updates, additions
src/test/java/.../EmploymentFormControllerTests.java, src/test/java/.../service/EmploymentFormServiceTest.java, src/test/java/.../security/SecurityIntegrationTests.java, src/test/java/org/example/cyberwatch/CyberWatchApplicationTests.java, src/test/resources/application-test.properties
Removed EmploymentFormControllerTests. Adjusted EmploymentFormServiceTest mocks to use CTO role. Added SecurityIntegrationTests to exercise role-based access across staff/forms endpoints. Enabled test profile for application tests and added test JWT secret property.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as EmploymentFormController
    participant Service as EmploymentFormService
    participant Security as MethodSecurityInterceptor
    participant Repo as Repository/DB

    Client->>Controller: HTTP request (e.g., GET /api/forms?status=...)
    Controller->>Service: getFormsByFilterApproval(status)
    Service->>Security: method security check (`@PreAuthorize`)
    Security-->>Service: allow / deny
    alt allowed
        Service->>Repo: findByStatus or findAll
        Repo-->>Service: entities
        Service-->>Controller: mapped DTOs
        Controller-->>Client: HTTP 200 with body
    else denied
        Security-->>Controller: AccessDeniedException
        Controller->>Client: HTTP 403 {"error":"Access denied"}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Ericthilen
  • alicewersen-rgb
  • gitnes94

"I hopped through code with a curious twitch,
Moved guards to services, trimmed a route switch.
Forms now find their place, errors call a name,
Tests hop in line, and roles play the game. 🥕🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Enhancement/refactor security in employment' is partially related to the changeset. It captures one significant aspect (security improvements via @PreAuthorize and authorization refactoring) but omits other major changes like exception handling redesign, controller endpoint consolidation, and testing updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhancement/refactorSecurityInEmployment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/example/cyberwatch/features/form/service/EmploymentFormService.java (1)

91-109: ⚠️ Potential issue | 🟠 Major

Align the in-method authorization with the new ADMIN allowance.

ADMIN now passes the Spring Security gate on Lines 91 and 156, but the business checks still only allow the creator or CEO/CTO. A non-creator admin will hit IllegalStateException instead of the behavior implied by the new annotations.

Also applies to: 156-170

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/cyberwatch/features/form/service/EmploymentFormService.java`
around lines 91 - 109, The creator-only business check in
updateFormBeforeApproval (and the similar post-approval update method around
lines 156-170) still throws IllegalStateException for non-creator ADMIN users
even though `@PreAuthorize` allows ADMIN; change the authorization logic to allow
the update when the logged-in user is either the form creator
(existingForm.getCreatedBy().getEmail().equals(loggedInHrEmail)) OR has the
ADMIN role. Implement this by checking the current Authentication authorities
(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) for
"ROLE_ADMIN" (or use an isAdmin() helper) and include that in the if-condition
that currently blocks non-creators; apply the same change to the other method
that enforces the creator check so ADMINs can proceed without hitting
IllegalStateException.
🧹 Nitpick comments (1)
src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java (1)

34-68: Add tests that prove the new service-layer role split.

These cases only validate the coarse URL rules. They do not exercise the new @PreAuthorize differences in src/main/java/org/example/cyberwatch/features/form/service/EmploymentFormService.java, such as HR being denied on approve/reject and CTO/CEO being denied on create/update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java`
around lines 34 - 68, Current tests only verify URL-level access; add
integration tests that exercise the service-layer role split implemented in
EmploymentFormService by invoking the controller endpoints that map to the
service methods for create/update and approve/reject (the endpoints that
delegate to EmploymentFormService.create/update and
EmploymentFormService.approve/reject). Specifically add tests using
`@WithMockUser`(role = "HR") asserting HR gets 403 when calling the approve/reject
endpoints, and tests using `@WithMockUser`(role = "CTO") and `@WithMockUser`(role =
"CEO") asserting they get 403 when calling the create/update endpoints; use
mockMvc to perform the appropriate POST/PUT requests and assert
status().isForbidden() (or isOk() for the allowed roles) so the `@PreAuthorize`
rules in EmploymentFormService are exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/test/java/org/example/cyberwatch/features/form/service/EmploymentFormServiceTest.java`:
- Around line 106-109: The test is incorrectly stubbing
staffRepository.findByEmail(anyString()) which returns the CTO for every email
and masks verification of the authenticated approver; change the stubs to match
the exact emails used in the test calls (e.g.,
when(staffRepository.findByEmail(eq("cto@cyberwatch.local"))).thenReturn(Optional.of(cto))
and
when(staffRepository.findByEmail(eq("manager@cyberwatch.local"))).thenReturn(Optional.of(manager)))
so service.rejectForm(id, "cto@cyberwatch.local") and the other cases exercise
lookup of the actual approver identity via staffRepository.findByEmail rather
than a wildcard; update all occurrences noted (lines around the existing
when(...) stubs and the other test blocks) to use eq(...) or specific string
arguments instead of anyString().

In
`@src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java`:
- Around line 52-57: The test method consultantCannotAccessForms targets a
removed route; update it to call the new EmploymentFormController endpoint by
changing the request from GET "/api/forms/pending" to GET "/api/forms" and
supply the status query parameter (e.g., status=pending) so the call exercises
the new handler mapping (use mockMvc.perform(get("/api/forms").param("status",
"pending"))), keeping the test expectation that a CONSULTANT receives
status().isForbidden().

---

Outside diff comments:
In
`@src/main/java/org/example/cyberwatch/features/form/service/EmploymentFormService.java`:
- Around line 91-109: The creator-only business check in
updateFormBeforeApproval (and the similar post-approval update method around
lines 156-170) still throws IllegalStateException for non-creator ADMIN users
even though `@PreAuthorize` allows ADMIN; change the authorization logic to allow
the update when the logged-in user is either the form creator
(existingForm.getCreatedBy().getEmail().equals(loggedInHrEmail)) OR has the
ADMIN role. Implement this by checking the current Authentication authorities
(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) for
"ROLE_ADMIN" (or use an isAdmin() helper) and include that in the if-condition
that currently blocks non-creators; apply the same change to the other method
that enforces the creator check so ADMINs can proceed without hitting
IllegalStateException.

---

Nitpick comments:
In
`@src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java`:
- Around line 34-68: Current tests only verify URL-level access; add integration
tests that exercise the service-layer role split implemented in
EmploymentFormService by invoking the controller endpoints that map to the
service methods for create/update and approve/reject (the endpoints that
delegate to EmploymentFormService.create/update and
EmploymentFormService.approve/reject). Specifically add tests using
`@WithMockUser`(role = "HR") asserting HR gets 403 when calling the approve/reject
endpoints, and tests using `@WithMockUser`(role = "CTO") and `@WithMockUser`(role =
"CEO") asserting they get 403 when calling the create/update endpoints; use
mockMvc to perform the appropriate POST/PUT requests and assert
status().isForbidden() (or isOk() for the allowed roles) so the `@PreAuthorize`
rules in EmploymentFormService are exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8015cff4-89ee-4943-806f-5e28558f0665

📥 Commits

Reviewing files that changed from the base of the PR and between eaf8793 and 0c05a9a.

📒 Files selected for processing (7)
  • src/main/java/org/example/cyberwatch/exception/GlobalExceptionHandler.java
  • src/main/java/org/example/cyberwatch/features/form/controller/EmploymentFormController.java
  • src/main/java/org/example/cyberwatch/features/form/exception/EmploymentFormNotFound.java
  • src/main/java/org/example/cyberwatch/features/form/service/EmploymentFormService.java
  • src/test/java/org/example/cyberwatch/features/form/EmploymentFormControllerTests.java
  • src/test/java/org/example/cyberwatch/features/form/service/EmploymentFormServiceTest.java
  • src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java
💤 Files with no reviewable changes (1)
  • src/test/java/org/example/cyberwatch/features/form/EmploymentFormControllerTests.java

Comment thread src/test/java/org/example/cyberwatch/SecurityIntegrationTests.java
…-based access. Enhance `GlobalExceptionHandler` to handle `AccessDeniedException`. Adjust `EmploymentFormServiceTest` to verify email usage logic.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java`:
- Line 67: Update the DisplayName annotation in the SecurityIntegrationTests
class to fix the typo: change the string in `@DisplayName`("HR should access form
endpoints and gett 400 for bad request in body") to use "get 400" instead of
"gett 400" so it reads `@DisplayName`("HR should access form endpoints and get 400
for bad request in body"); locate this annotation in
SecurityIntegrationTests.java to make the edit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 751a8ff8-bc31-488c-851e-19d78a79149e

📥 Commits

Reviewing files that changed from the base of the PR and between 0c05a9a and ab172e6.

📒 Files selected for processing (3)
  • src/main/java/org/example/cyberwatch/exception/GlobalExceptionHandler.java
  • src/test/java/org/example/cyberwatch/features/form/service/EmploymentFormServiceTest.java
  • src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/org/example/cyberwatch/features/form/service/EmploymentFormServiceTest.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java (1)

65-65: ⚠️ Potential issue | 🟡 Minor

Fix typo in display name text.

Line 65 has "gett 400"; this should be "get 400".

Suggested fix
-    `@DisplayName`("HR should access form endpoints and gett 400 for bad request in body")
+    `@DisplayName`("HR should access form endpoints and get 400 for bad request in body")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java`
at line 65, Fix the typo in the DisplayName annotation string for the test in
SecurityIntegrationTests: change the text in the `@DisplayName` on the test method
annotated in class SecurityIntegrationTests from "HR should access form
endpoints and gett 400 for bad request in body" to "HR should access form
endpoints and get 400 for bad request in body" so the word "gett" becomes "get".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java`:
- Line 65: Fix the typo in the DisplayName annotation string for the test in
SecurityIntegrationTests: change the text in the `@DisplayName` on the test method
annotated in class SecurityIntegrationTests from "HR should access form
endpoints and gett 400 for bad request in body" to "HR should access form
endpoints and get 400 for bad request in body" so the word "gett" becomes "get".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7bbc5afc-863e-421c-9417-61d9d77e1f6b

📥 Commits

Reviewing files that changed from the base of the PR and between ab172e6 and 46b5ae5.

📒 Files selected for processing (3)
  • src/test/java/org/example/cyberwatch/CyberWatchApplicationTests.java
  • src/test/java/org/example/cyberwatch/features/security/SecurityIntegrationTests.java
  • src/test/resources/application-test.properties
✅ Files skipped from review due to trivial changes (2)
  • src/test/resources/application-test.properties
  • src/test/java/org/example/cyberwatch/CyberWatchApplicationTests.java

…update or delete forms, refactor permission logic, and update related tests.
@codebyNorthsteep
codebyNorthsteep merged commit 5216bc1 into main Apr 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant