Skip to content

Adds the security layer to the Comment package, and updates tests.#39

Merged
Martin-E-Karlsson merged 2 commits into
mainfrom
feature/comment-security
Apr 21, 2026
Merged

Adds the security layer to the Comment package, and updates tests.#39
Martin-E-Karlsson merged 2 commits into
mainfrom
feature/comment-security

Conversation

@EdvinSandgren

@EdvinSandgren EdvinSandgren commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Comments now use the ID of the authenticated user, rather than requiring it to be set in the DTO

Summary by CodeRabbit

  • Security

    • Comment endpoints now require authenticated users.
  • API Changes

    • Creating a comment no longer accepts an authorId; the authenticated user is used as the author.
    • Comment responses no longer include a comment ID field; payloads now contain visaId, authorName, text, and createdAt.
  • Tests

    • Tests updated to authenticate test users, clear security context after runs, and to match the revised request/response shapes.

Comments now use the ID of the authenticated user, rather than requiring it to be set in the DTO
@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Class-level authentication was added to CommentController; comment creation now uses the authenticated UserPrincipal (userId forwarded to CommentService). DTOs were changed: removed authorId from CreateCommentDTO and id from CommentDTO. Mapper, service signature, and tests updated accordingly.

Changes

Cohort / File(s) Summary
Controller / Security
src/main/java/org/example/visacasemanagementsystem/comment/controller/CommentController.java
Added @PreAuthorize("isAuthenticated()") at class level and accept @AuthenticationPrincipal UserPrincipal principal in createComment, forwarding principal.getUserId() to service.
DTOs
src/main/java/org/example/visacasemanagementsystem/comment/dto/CommentDTO.java, src/main/java/org/example/visacasemanagementsystem/comment/dto/CreateCommentDTO.java
Removed id from CommentDTO and removed authorId (and its validation) from CreateCommentDTO; request payloads no longer include authorId.
Service / Mapper
src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java, src/main/java/org/example/visacasemanagementsystem/comment/mapper/CommentMapper.java
Changed CommentService.createComment signature to accept Long userId; service now loads author by userId. Mapper builds CommentDTO without id.
Tests
src/test/java/org/example/visacasemanagementsystem/comment/CommentControllerTest.java, .../CommentMapperTest.java, .../CommentServiceIntegrationTest.java, .../visa/VisaViewControllerTest.java
Updated test fixtures, assertions, and mocks to match DTO/signature changes; added test authentication setup and SecurityContext clearing hooks.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as CommentController
    participant Security as SecurityContextHolder
    participant Service as CommentService
    participant UserRepo as UserRepository
    participant DB as Database

    Client->>Controller: POST /api/comments (CreateCommentDTO{visaId, text})
    Controller->>Controller: `@PreAuthorize` isAuthenticated()
    Controller->>Security: retrieve Authentication -> principal
    Security-->>Controller: UserPrincipal (userId)
    Controller->>Service: createComment(dto, userId)
    Service->>UserRepo: findById(userId)
    UserRepo->>DB: query user by id
    DB-->>UserRepo: User entity
    UserRepo-->>Service: User (author)
    Service->>DB: save Comment(visaId, author, text)
    DB-->>Service: persisted Comment
    Service->>Controller: CommentDTO(visaId, authorName, text, createdAt)
    Controller-->>Client: 201 Created (CommentDTO)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • eeebbaandersson
  • Martin-E-Karlsson

Poem

🐇 I hopped in quietly, principal in paw,
Removed author IDs from the client draw.
Now authenticated ears hear who speaks,
Server assigns authors — no more leaks.
Hop, comment, hop — secure and small hurrah! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding authentication/security to the Comment package and updating corresponding tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feature/comment-security

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.

Caution

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

⚠️ Outside diff range comments (1)
src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java (1)

47-77: ⚠️ Potential issue | 🟡 Minor

Clear SecurityContextHolder between tests to avoid state leakage.

SecurityContextHolder is backed by a thread-local strategy by default; @Transactional rolls back DB state but does not clear the security context. Tests that run after createComment_shouldSaveAndRetrieveComment / createComment_shouldThrowException_WhenUserDoesNotExist will inherit whatever Authentication was last set, which can mask bugs (e.g. createComment_shouldThrowException_WhenTextIsBlank currently relies on the text check short-circuiting before the principal is read — if that ordering ever changes, a leaked auth would silently keep the test green).

🧪 Proposed fix
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
@@
     `@BeforeEach`
     void setUp() {
@@
     }
+
+    `@AfterEach`
+    void tearDown() {
+        SecurityContextHolder.clearContext();
+    }

Also applies to: 164-176

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

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`
around lines 47 - 77, The tests leak authentication state because
SecurityContextHolder (thread-local) isn’t cleared between tests; update
CommentServiceIntegrationTest by adding an `@AfterEach` teardown (or adding a
clear at the end of setUp) that calls SecurityContextHolder.clearContext() so no
Authentication from prior tests (e.g.,
createComment_shouldSaveAndRetrieveComment,
createComment_shouldThrowException_WhenUserDoesNotExist,
createComment_shouldThrowException_WhenTextIsBlank) persists and masks failures;
locate the setUp() method and add a teardown method (or clear) referencing
SecurityContextHolder.clearContext().
🧹 Nitpick comments (2)
src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java (1)

58-65: nonExistentUser fixture is fragile.

Two small issues:

  • nonExistentUser is never persisted, but sets email/username identical to testUser. That's misleading and would immediately break if someone later decides to persist this fixture for another test.
  • Hardcoding setId(999L) couples the test to the assumption that 999 is never generated by the DB sequence in this Spring context. Harmless today, but swapping to a distinctly out-of-range value (e.g. Long.MAX_VALUE) or deriving the id from userRepository.findAll().size() + 1000L makes the intent — "an id that definitely doesn't exist" — unambiguous.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`
around lines 58 - 65, The nonExistentUser fixture (nonExistentUser) is fragile
because it duplicates testUser's email/username and uses a hardcoded id
(setId(999L)); update the fixture to use a clearly unique email/username (e.g.,
suffix with "-nonexistent" or similar) and remove the brittle hardcoded
id—instead assign an unmistakably out-of-range id (e.g., Long.MAX_VALUE) or
compute one relative to userRepository (e.g., userRepository.findAll().size() +
1000L) to guarantee it does not exist, and ensure nonExistentUser remains
unpersisted in tests that expect a missing entity.
src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java (1)

53-61: Brittle authentication resolution — add defensive null/type checks and consider controller-level resolution.

The unchecked (UserPrincipal) cast will throw ClassCastException if the principal is ever a different type. While integration tests correctly set up UserPrincipal, any test or caller using @WithMockUser without manual principal setup, or invoking the service outside HTTP context (schedulers, other services), bypasses the @PreAuthorize guard and risks runtime failure.

Additionally:

  • Objects.requireNonNull(...) without a message yields opaque NullPointerException instead of a meaningful auth error
  • The nested chain is hard to scan

Two paths forward:

  1. In-place hardening (if keeping auth resolution in service): Check the principal type explicitly and throw AuthenticationCredentialsNotFoundException with a clear message
  2. Idiomatic approach (recommended): Resolve the principal in the controller via @AuthenticationPrincipal UserPrincipal principal and pass principal.getUserId() to the service, per Spring MVC best practices
♻️ In-place hardening example
-        Long authorId = ((UserPrincipal) Objects
-                .requireNonNull(Objects
-                        .requireNonNull(SecurityContextHolder
-                                .getContext()
-                                .getAuthentication())
-                        .getPrincipal()))
-                .getUserId();
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null || !(authentication.getPrincipal() instanceof UserPrincipal principal)) {
+            throw new AuthenticationCredentialsNotFoundException("No authenticated user available");
+        }
+        Long authorId = principal.getUserId();

Or move resolution to the controller:

`@PostMapping`
public ResponseEntity<CommentDTO> createComment(`@Valid` `@RequestBody` CreateCommentDTO dto,
                                                `@AuthenticationPrincipal` UserPrincipal principal) {
    return new ResponseEntity<>(commentService.createComment(dto, principal.getUserId()), HttpStatus.CREATED);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`
around lines 53 - 61, The current cast of getPrincipal() to UserPrincipal in
CommentService (the Long authorId = ((UserPrincipal) ...).getUserId() block) is
brittle and can throw ClassCastException/opaque NPEs; either (preferred) move
principal resolution into the controller and pass principal.getUserId() into
CommentService.createComment(...) using `@AuthenticationPrincipal` UserPrincipal
principal, or (if keeping service-side) replace the nested
Objects.requireNonNull chain with explicit checks: retrieve Authentication via
SecurityContextHolder.getContext().getAuthentication(), if null throw
AuthenticationCredentialsNotFoundException with a clear message, check that
authentication.getPrincipal() is an instance of UserPrincipal before casting and
throw AuthenticationCredentialsNotFoundException (with message) if not, then
extract userId and continue to findById(userRepository).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`:
- Around line 47-77: The tests leak authentication state because
SecurityContextHolder (thread-local) isn’t cleared between tests; update
CommentServiceIntegrationTest by adding an `@AfterEach` teardown (or adding a
clear at the end of setUp) that calls SecurityContextHolder.clearContext() so no
Authentication from prior tests (e.g.,
createComment_shouldSaveAndRetrieveComment,
createComment_shouldThrowException_WhenUserDoesNotExist,
createComment_shouldThrowException_WhenTextIsBlank) persists and masks failures;
locate the setUp() method and add a teardown method (or clear) referencing
SecurityContextHolder.clearContext().

---

Nitpick comments:
In
`@src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`:
- Around line 53-61: The current cast of getPrincipal() to UserPrincipal in
CommentService (the Long authorId = ((UserPrincipal) ...).getUserId() block) is
brittle and can throw ClassCastException/opaque NPEs; either (preferred) move
principal resolution into the controller and pass principal.getUserId() into
CommentService.createComment(...) using `@AuthenticationPrincipal` UserPrincipal
principal, or (if keeping service-side) replace the nested
Objects.requireNonNull chain with explicit checks: retrieve Authentication via
SecurityContextHolder.getContext().getAuthentication(), if null throw
AuthenticationCredentialsNotFoundException with a clear message, check that
authentication.getPrincipal() is an instance of UserPrincipal before casting and
throw AuthenticationCredentialsNotFoundException (with message) if not, then
extract userId and continue to findById(userRepository).

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`:
- Around line 58-65: The nonExistentUser fixture (nonExistentUser) is fragile
because it duplicates testUser's email/username and uses a hardcoded id
(setId(999L)); update the fixture to use a clearly unique email/username (e.g.,
suffix with "-nonexistent" or similar) and remove the brittle hardcoded
id—instead assign an unmistakably out-of-range id (e.g., Long.MAX_VALUE) or
compute one relative to userRepository (e.g., userRepository.findAll().size() +
1000L) to guarantee it does not exist, and ensure nonExistentUser remains
unpersisted in tests that expect a missing entity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de8425c5-d518-438f-90f0-6b02480efb15

📥 Commits

Reviewing files that changed from the base of the PR and between 620e00a and 75bf966.

📒 Files selected for processing (9)
  • src/main/java/org/example/visacasemanagementsystem/comment/controller/CommentController.java
  • src/main/java/org/example/visacasemanagementsystem/comment/dto/CommentDTO.java
  • src/main/java/org/example/visacasemanagementsystem/comment/dto/CreateCommentDTO.java
  • src/main/java/org/example/visacasemanagementsystem/comment/mapper/CommentMapper.java
  • src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java
  • src/test/java/org/example/visacasemanagementsystem/comment/CommentControllerTest.java
  • src/test/java/org/example/visacasemanagementsystem/comment/CommentMapperTest.java
  • src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaViewControllerTest.java
💤 Files with no reviewable changes (4)
  • src/main/java/org/example/visacasemanagementsystem/comment/mapper/CommentMapper.java
  • src/main/java/org/example/visacasemanagementsystem/comment/dto/CreateCommentDTO.java
  • src/main/java/org/example/visacasemanagementsystem/comment/dto/CommentDTO.java
  • src/test/java/org/example/visacasemanagementsystem/comment/CommentMapperTest.java

@eeebbaandersson eeebbaandersson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Snyggt uppdaterat här med!

@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.

🧹 Nitpick comments (3)
src/test/java/org/example/visacasemanagementsystem/comment/CommentControllerTest.java (1)

66-85: Move SecurityContextHolder cleanup into @AfterEach.

Cleanup currently lives at the end of createComment_shouldReturnCreated only, and will be skipped if an assertion fails earlier, leaking the manually-set UserPrincipal into subsequent tests (e.g. getCommentsByVisa_ShouldReturnList, which is not covered by @WithMockUser's context cleanup for the manually-injected authentication). The integration test in this PR already uses an @AfterEach pattern — worth mirroring here for consistency and robustness.

♻️ Suggested change
+    `@org.junit.jupiter.api.AfterEach`
+    void tearDown() {
+        SecurityContextHolder.clearContext();
+    }
+
     `@Test`
     `@WithMockUser`
     void createComment_shouldReturnCreated() throws Exception {
         ...
                 .andExpect(jsonPath("$.authorName").value("Test User"));
-
-        // Cleanup
-        SecurityContextHolder.clearContext();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentControllerTest.java`
around lines 66 - 85, Move the manual SecurityContextHolder.clearContext() call
out of the end of createComment_shouldReturnCreated and into a dedicated
`@AfterEach` teardown method so the authentication set by the test's UserPrincipal
is always cleared even on failures; add a method annotated with `@AfterEach`
(e.g., void tearDown()) that calls SecurityContextHolder.clearContext(),
ensuring tests like getCommentsByVisa_ShouldReturnList (and any tests using
`@WithMockUser` or manual authentication) do not leak authentication state between
runs.
src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java (2)

59-67: nonExistentUser setup: confirm setId(Long.MAX_VALUE) won't collide and is never persisted.

The fixture sets an explicit id and is intentionally not saved, which works as long as (a) no other row in the test DB has id Long.MAX_VALUE, and (b) nothing downstream cascades a save of nonExistentUser. Both hold today, but a safer pattern is to compute a definitely-unused id dynamically (e.g. userRepository.findAll().stream().mapToLong(User::getId).max().orElse(0L) + 1_000_000L) so the test remains robust if @GeneratedValue strategy or data seeding changes.

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

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`
around lines 59 - 67, The test builds a nonExistentUser by calling
User#setId(Long.MAX_VALUE) which risks colliding with persisted IDs or
accidental persistence; instead compute a guaranteed-unused id at runtime (e.g.,
query userRepository to get the current max id and add an offset) and assign
that to nonExistentUser via setId, ensuring nonExistentUser remains unsaved;
update the fixture setup in CommentServiceIntegrationTest to use userRepository
(or a repository helper) to derive the safe id rather than using Long.MAX_VALUE
and ensure no subsequent code calls save on nonExistentUser.

94-131: Authentication helpers are effectively no-ops in these service-level tests.

CommentService.createComment reads the user from its userId parameter, not from SecurityContextHolder, so authenticateTestUser() / authenticateNonExistentUser() don't influence behavior here — the id passed as the second argument is what drives the outcome. That's fine for now, but it can be misleading to future readers (suggesting the service somehow consults the security context).

Consider either:

  • dropping the authenticate* calls from these service tests and keeping them only in controller-layer tests, or
  • adding a short comment noting that the context is set purely to mirror production call conditions.

Not blocking.

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

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`
around lines 94 - 131, The authentication helper calls (authenticateTestUser and
authenticateNonExistentUser) in these service-level tests are no-ops for
CommentService.createComment because the service uses the explicit userId
parameter; remove those authenticate* calls from the tests (e.g., in
createComment_successful,
createComment_shouldThrowException_WhenUserDoesNotExist,
createComment_shouldThrowException_WhenVisaDoesNotExist) to avoid confusion, or
if you prefer to keep them for parity with controller tests add a single
clarifying inline comment above each test referencing that CommentService.reads
user by the userId param (not SecurityContextHolder) so future readers aren’t
misled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentControllerTest.java`:
- Around line 66-85: Move the manual SecurityContextHolder.clearContext() call
out of the end of createComment_shouldReturnCreated and into a dedicated
`@AfterEach` teardown method so the authentication set by the test's UserPrincipal
is always cleared even on failures; add a method annotated with `@AfterEach`
(e.g., void tearDown()) that calls SecurityContextHolder.clearContext(),
ensuring tests like getCommentsByVisa_ShouldReturnList (and any tests using
`@WithMockUser` or manual authentication) do not leak authentication state between
runs.

In
`@src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java`:
- Around line 59-67: The test builds a nonExistentUser by calling
User#setId(Long.MAX_VALUE) which risks colliding with persisted IDs or
accidental persistence; instead compute a guaranteed-unused id at runtime (e.g.,
query userRepository to get the current max id and add an offset) and assign
that to nonExistentUser via setId, ensuring nonExistentUser remains unsaved;
update the fixture setup in CommentServiceIntegrationTest to use userRepository
(or a repository helper) to derive the safe id rather than using Long.MAX_VALUE
and ensure no subsequent code calls save on nonExistentUser.
- Around line 94-131: The authentication helper calls (authenticateTestUser and
authenticateNonExistentUser) in these service-level tests are no-ops for
CommentService.createComment because the service uses the explicit userId
parameter; remove those authenticate* calls from the tests (e.g., in
createComment_successful,
createComment_shouldThrowException_WhenUserDoesNotExist,
createComment_shouldThrowException_WhenVisaDoesNotExist) to avoid confusion, or
if you prefer to keep them for parity with controller tests add a single
clarifying inline comment above each test referencing that CommentService.reads
user by the userId param (not SecurityContextHolder) so future readers aren’t
misled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 71ef68da-0e75-4a68-ae03-8f264d9d36c0

📥 Commits

Reviewing files that changed from the base of the PR and between 75bf966 and 7ef1d04.

📒 Files selected for processing (5)
  • src/main/java/org/example/visacasemanagementsystem/comment/controller/CommentController.java
  • src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java
  • src/test/java/org/example/visacasemanagementsystem/comment/CommentControllerTest.java
  • src/test/java/org/example/visacasemanagementsystem/comment/CommentServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaViewControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaViewControllerTest.java

@Martin-E-Karlsson Martin-E-Karlsson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bra jobbat! Jag tror vi kan göra en merge med en gång!

@Martin-E-Karlsson
Martin-E-Karlsson merged commit 14a5c60 into main Apr 21, 2026
2 checks 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.

3 participants