Fix spoofing vulnerability (Resolves #15)#75
Conversation
omatheusmesmo
left a comment
There was a problem hiding this comment.
Pull Request Description
This PR implements a complete fix for an ID Spoofing (IDOR) vulnerability that allowed authenticated users to access or modify resources belonging to other users by manipulating user IDs in request payloads.
Related Task
Resolves #15
What was done?
- Updated the controllers to use
@AuthenticationPrincipal, allowing the authenticated user to be automatically injected from theSecurityContextHolder.- Modified the
Userentity to implementUserDetails, enabling Spring Security to manage it directly after authentication through the JWT filter.- Added ownership validation in the service layer to ensure that the logged-in user is authorized to perform actions on the requested resources. Before executing any update, delete, or access operation, the system verifies whether the authenticated user matches the resource owner. If not, a
ResourceOwnershipExceptionis thrown and an HTTP 403 Forbidden response is returned.- Removed the
idUserfield from request DTOs to eliminate the spoofing vector.Tests
- I tested the changes locally and added both unit and integration tests.
How to Test
Authenticate as User A and create a shopping list.
Authenticate as User B.
Attempt to:
- Access User A's shopping list
- Update it
- Delete it
Verify that the API returns HTTP 403 Forbidden.
Confirm that User A can still access and manage their own resources normally.
Useful Links
Hello @felipemelozx, congratulations on your first PR, thanks for it! Please run mvn net.revelc.code.formatter:formatter-maven-plugin:2.29.0:format to resolve the style formatting issue.
4d3df7c to
dd05f14
Compare
omatheusmesmo
left a comment
There was a problem hiding this comment.
Code Review - PR #75 (Fix spoofing Vulnerability)
Hello, @felipemelozx! Great job on addressing the spoofing vulnerability (#15). Migrating to @AuthenticationPrincipal and cleaning up the DTOs are excellent steps toward a more secure and standard Spring Security implementation.
I have analyzed the changes and identified some points for refinement to ensure we maintain both performance and the core collaborative features of ShoppMate.
1. Collaborative Access vs. Data Redundancy
The current implementation in ShoppingListService restricts access exclusively to the owner. While this fixes the spoofing, it inadvertently disables the ListPermission system (the Many-to-Many relationship between Users and Lists).
- The Issue: If two users need to share a list, they are now forced to create two identical list records in the database because the system no longer recognizes shared permissions. This leads to data redundancy and loss of synchronization.
- The Fix: Access logic should allow both the
ownerOR any user with a valid entry inlist_permissions. This preserves the Many-to-Many architecture designed for collaboration.
2. Performance: Unified Fetch & Verify
Currently, the Controllers perform two separate queries: one to verify ownership and another to fetch the data.
- Optimization: Instead of a separate
verifyOwnershipcall, we should implement a unified method that fetches the entity and checks permissions. By reusing the existingfindListById(id), we keep the code DRY while ensuring only one query is made to fetch the list.- Example (Repository):
// ListPermissionRepository.java boolean existsByShoppingListIdAndUserIdAndDeletedFalse(Long listId, Long userId);
- Example (Service):
public ShoppingList findAndVerifyAccess(Long listId, User user) { ShoppingList list = findListById(listId); // Reuses existing fetch & exception logic boolean isOwner = list.getOwner().getId().equals(user.getId()); boolean hasPermission = permissionRepository.existsByShoppingListIdAndUserIdAndDeletedFalse(listId, user.getId()); if (!isOwner && !hasPermission) { throw new ResourceOwnershipException("Access Denied: You do not have permission to access this list."); } return list; }
3. Fetch Type EAGER in Item.java
The change from LAZY to EAGER for Category and Unit in the Item entity is concerning.
- N+1 Risk: This may trigger unnecessary joins or multiple queries when listing items, significantly impacting performance as the database grows.
- Scope: This change appears unrelated to the spoofing fix. I recommend reverting to
LAZYto keep the implementation focused and performant.
These refinements will ensure that ShoppMate remains secure without sacrificing its collaborative nature or database efficiency. Great foundation on the security layer!
|
🤖 Hi @felipemelozx, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @felipemelozx, but I was unable to process your request. Please see the logs for more details. |
|
@felipemelozx , there is some git conflicts, could you please fix them? |
|
Got it, I'll fix the conflicts. |
|
🤖 Hi @felipemelozx, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @felipemelozx, but I was unable to process your request. Please see the logs for more details. |
|
🤖 Hi @felipemelozx, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @felipemelozx, but I was unable to process your request. Please see the logs for more details. |
@felipemelozx ,Something went wrong during the merge. See the CI logs for details. |
|
🤖 Hi @felipemelozx, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR implements user-based access control throughout the shopping list API by extracting authenticated users from Spring Security's context rather than relying on request body IDs. Key changes include updating the User entity to implement UserDetails, injecting authenticated users via Changes
Sequence DiagramsequenceDiagram
actor Client
participant Controller as Spring MVC<br/>Controller
participant Security as Security<br/>Context
participant Service as Service<br/>Layer
participant Repository as JPA<br/>Repository
participant Database as Database
Client->>Controller: HTTP Request + JWT
Controller->>Security: Extract Auth
Security-->>Controller: User Principal
Controller->>Service: invoke(resource_id, User)
Service->>Service: Verify Ownership<br/>via findAndVerifyAccess()
alt User Owns Resource
Service->>Repository: fetch with user scope
Repository->>Database: SELECT WHERE user_id = ?
Database-->>Repository: Resource Data
Repository-->>Service: Entity
Service-->>Controller: Success Response
Controller-->>Client: HTTP 200 + Data
else User Lacks Access
Service-->>Controller: ResourceOwnershipException
Controller-->>Client: HTTP 403 Forbidden
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
🤖 I'm sorry @felipemelozx, but I was unable to process your request. Please see the logs for more details. |
4ad5927 to
27ebf42
Compare
d2d1107 to
d613db3
Compare
|
@felipemelozx , hi! I made a huge change in the develop branch and there is more conflicts, could you please fix again? Thanks. |
|
Hi @felipemelozx ! A better approach would be update your remote repository and then run the following commands inside your local feature branch. |
|
Hi @omatheusmesmo ! I ran the commands but nothing changed in my branch. Do you think it would be better to create a new branch from the latest develop and move the changes there? It might be easier to work from a clean branch. |
@felipemelozx did you update your remote branch first? |
|
Yes, I updated it. |
Oh, that's weird if 'git status' doesn't show commits to push. You don't need to create a new branch on the remote. You can delete your local branch and recreate it from the updated main. Just be sure to stash your changes to this feature before deleting the branch. In the end, you can run 'git push -f' to update the remote branch. Hope works fine. |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/src/main/java/com/omatheusmesmo/shoppmate/auth/configs/RsaKeyConfig.java (1)
26-33:⚠️ Potential issue | 🟠 MajorRestore resource validation and fix resource handling for JWT key loading
Lines 26 and 38 load keys from externally configured
Resourcevalues without validating existence, readability, or scheme. This allows unsafe key origins via environment variable override (e.g.,JWT_PRIVATE_KEY_PATH=https://attacker.com/key.pem), weakening trust boundaries for signing/verification keys. Additionally, theInputStreamcreated bygetInputStream()is never explicitly closed, andnew String(byte[])lacks an explicit charset.🔧 Proposed fix
@@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; @@ `@Bean` public RSAPrivateKey privateKey() throws Exception { + validateResource(privateKeyResource, "jwt.private-key"); - String key = new String(privateKeyResource.getInputStream().readAllBytes()); + String key = new String(privateKeyResource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); key = key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s", ""); @@ `@Bean` public RSAPublicKey publicKey() throws Exception { + validateResource(publicKeyResource, "jwt.public-key"); - String key = new String(publicKeyResource.getInputStream().readAllBytes()); + String key = new String(publicKeyResource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); key = key.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replaceAll("\\s", ""); @@ + private void validateResource(Resource resource, String propertyName) throws Exception { + if (!resource.exists() || !resource.isReadable()) { + throw new IllegalStateException("Invalid key resource for " + propertyName); + } + URI uri = resource.getURI(); + String scheme = uri.getScheme(); + if (scheme != null && !scheme.equals("file") && !scheme.equals("classpath")) { + throw new IllegalStateException("Unsupported key resource scheme for " + propertyName + ": " + scheme); + } + } }Also applies to: 38-45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/com/omatheusmesmo/shoppmate/auth/configs/RsaKeyConfig.java` around lines 26 - 33, In RsaKeyConfig, validate and safely load the RSA keys: before reading privateKeyResource (and likewise publicKeyResource), check resource.exists() and resource.isReadable(), and reject or throw if the resource URL has an untrusted remote scheme (e.g., only allow "file" or "classpath" schemes) to prevent loading from attacker-controlled URLs; read the stream using a try-with-resources (or explicitly close the InputStream) and construct the String with an explicit charset (StandardCharsets.UTF_8) instead of new String(byte[]), then proceed to Base64-decode and build the PKCS8EncodedKeySpec; apply the same validation/stream-closing/charset fixes to the public key loading path as well.README.md (1)
74-100:⚠️ Potential issue | 🟡 MinorDuplicate and mislabeled sections in manual setup instructions.
There appear to be merge artifacts or copy-paste errors:
- Lines 74-79: Labeled "Frontend" but contain Java/Maven backend commands (should likely be removed or correctly labeled)
- Lines 92-100: Duplicate the npm instructions already present in lines 82-91
📝 Proposed fix to remove duplicates and fix mislabeling
-#### Frontend -2. Ensure you have Java 17 installed. -3. Run with: - ```bash - ./mvnw spring-boot:run - ``` - #### Frontend 1. Navigate to `frontend/`. 2. Install dependencies: ```bash npm install ``` 3. Run with: ```bash npm start ``` 4. Access at [http://localhost:4200](http://localhost:4200). -2. Install dependencies: - ```bash - npm install - ``` -3. Run with: - ```bash - npm start - ``` -4. Access at [http://localhost:4200](http://localhost:4200).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 74 - 100, Remove the duplicate and mislabelled sections in the README by deleting the Maven/backend snippet that is incorrectly under the "Frontend" heading and removing the repeated npm install/start block; ensure there is a single correctly labeled "Backend" or "Server" section containing the Java 17 and ./mvnw spring-boot:run instructions and a single "Frontend" section that lists Navigate to `frontend/`, npm install, npm start, and the http://localhost:4200 URL so the README no longer contains duplicated npm instructions or the misplaced Maven commands.
🧹 Nitpick comments (7)
.github/workflows/gemini-triage.yml (1)
62-62: Consider pinningrun-gemini-clito a specific commit SHA.The action uses
@v0which is a floating tag that may change unexpectedly. While the# ratchet:excludecomment indicates this is intentional, floating tags can introduce breaking changes or security issues without notice.As per coding guidelines: "Use specific action versions (commit SHA or tagged versions)." Consider pinning to a commit SHA once the action stabilizes, or document the rationale for using the floating tag.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/gemini-triage.yml at line 62, The workflow currently references the floating tag 'google-github-actions/run-gemini-cli@v0' (with '# ratchet:exclude') which can change; update the workflow to pin the action to a specific commit SHA or stable tag (or add a short justification comment if you must keep the floating tag) so CI behavior is deterministic; locate the uses line referencing 'google-github-actions/run-gemini-cli@v0' and replace '@v0' with a concrete '@<commit-sha>' or '@<tag>' (or add an explicit rationale near the '# ratchet:exclude' comment).backend/src/test/java/com/omatheusmesmo/shoppmate/service/ItemServiceTest.java (1)
109-127: Please make sure the real query is exercised outside Mockito.These changes only swap the mocked repository method, so this suite still won't catch a broken
findByIdWithRelations(...)implementation or regressions in thedeleted = falsebehavior. If that coverage does not already exist elsewhere, add a repository/integration test for the new lookup.Also applies to: 136-186
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/test/java/com/omatheusmesmo/shoppmate/service/ItemServiceTest.java` around lines 109 - 127, The test ItemServiceTest currently only mocks itemRepository.findByIdWithRelations(...) so it doesn't exercise the real repository query or the deleted = false filter; add an integration/repository test that uses the actual ItemRepository (not Mockito) to persist an Item with deleted=false and another with deleted=true, then call the real repository method findByIdWithRelations(id) (or the JPA query backing it) to assert the deleted=true item is not returned and deleted=false is returned; ensure the test class boots Spring context or uses `@DataJpaTest` and saves entities via ItemRepository before invoking findByIdWithRelations to verify the real query behavior and prevent regressions.backend/src/main/java/com/omatheusmesmo/shoppmate/item/service/ItemService.java (1)
55-57: Separate the read lookup from the write precheck.This method is now doing double duty: read paths need the richer load, but
removeItem()/editItem()only use it to confirm existence, andfindItem(Item)still follows a different lookup path. Splitting those responsibilities would keep lookup semantics consistent and avoid extra work on mutation flows. As per coding guidelines, "You are a Senior Java Architect and Spring Boot expert. You focus on writing high-performance, secure, and maintainable microservices using modern Java 17 patterns."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/com/omatheusmesmo/shoppmate/item/service/ItemService.java` around lines 55 - 57, Split the responsibilities of ItemService.findById into a rich-read method and a lightweight existence check: keep a new read method (e.g., findByIdWithRelations or keep findById but ensure it uses itemRepository.findByIdWithRelations(id) and throws NoSuchElementException) for read paths that need full relations, and add a separate precheck method (e.g., ensureExistsById or existsByIdCheck) that calls a lightweight repository lookup (itemRepository.existsById(id) or itemRepository.findById(id) without loading relations) and throws the same NoSuchElementException for mutation flows; then update mutation methods like removeItem and editItem to call the new precheck method instead of the heavy findByIdWithRelations call so edits/removals don't incur the extra relation loading.backend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListController.java (1)
22-22: Remove stale TODO comment.This TODO has been addressed by the changes in this PR—
getAllShoppingLists(line 39) now callsservice.findAllByUser(user), which returns only lists owned by or shared with the authenticated user.♻️ Proposed fix
-// TODO: return only lists owned by the user or that are shared with him `@RestController` `@RequestMapping`("/lists")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListController.java` at line 22, Remove the stale TODO comment in ShoppingListController.java; the functionality is already implemented as getAllShoppingLists now calls service.findAllByUser(user) which returns only lists owned by or shared with the authenticated user, so delete the "// TODO: return only lists owned by the user or that are shared with him" line to avoid confusion.backend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionService.java (3)
22-30: Consider making injected dependenciesfinal.The coding guidelines specify constructor injection with
finalfields. These fields are assigned only in the constructor and should be markedfinalto ensure immutability and clearly express intent.♻️ Proposed fix
- private final ListPermissionRepository listPermissionRepository; + private final ListPermissionRepository listPermissionRepository; - private final ShoppingListService shoppingListService; + private final ShoppingListService shoppingListService; - private final UserService userService; + private final UserService userService; - private final AuditService auditService; + private final AuditService auditService; - private final ListPermissionMapper listPermissionMapper; + private final ListPermissionMapper listPermissionMapper;Note: The fields are already
final. No change needed here.On closer inspection, the fields are already
final. LGTM on dependency injection.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionService.java` around lines 22 - 30, The injected dependencies in ListPermissionService (listPermissionRepository, shoppingListService, userService, auditService, listPermissionMapper) are already declared final, so no code changes are needed; simply confirm the constructor performs proper constructor injection and leave the final fields as-is.
45-48: Extract ownership verification to a helper method.The ownership check pattern is repeated across
addListPermission,findListUserPermissionById, andfindAllPermissionsByListId. Extracting this to a private helper would improve maintainability and provide a single place for potential null-safety improvements.♻️ Proposed helper method
private void verifyOwnership(ShoppingList list, User user, String message) { if (!list.getOwner().getId().equals(user.getId())) { throw new ResourceOwnershipException(message); } }Usage example:
-if (!shoppingList.getOwner().getId().equals(requester.getId())) { - throw new ResourceOwnershipException( - "User does not have permission to grant access to this list. Only the list owner can grant permissions."); -} +verifyOwnership(shoppingList, requester, + "User does not have permission to grant access to this list. Only the list owner can grant permissions.");Also applies to: 78-80, 103-105
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionService.java` around lines 45 - 48, Extract the repeated ownership check in ListPermissionService into a private helper method verifyOwnership(ShoppingList list, User user, String message) that throws ResourceOwnershipException when list.getOwner().getId().equals(user.getId()) is false; replace the inline checks in addListPermission, findListUserPermissionById, and findAllPermissionsByListId with calls to verifyOwnership(shoppingList, requester, "<same message as original>") to centralize null-safety and error messaging (ensure you call getOwner() and getId() safely as needed inside the helper).
100-108: Consider simplifying by removing redundant access check.
findAndVerifyAccess(line 101) verifies the user has access (owner OR shared), but lines 103-105 then reject non-owners anyway. Since only owners can proceed, the initial access check is redundant—you could call a simplefindListByIdinstead.However, if
findAndVerifyAccessalso verifies the list exists and isn't deleted, keeping it might be intentional for consistency.♻️ Potential simplification
public List<ListPermission> findAllPermissionsByListId(Long id, User user) { - ShoppingList list = shoppingListService.findAndVerifyAccess(id, user); - - if (!list.getOwner().getId().equals(user.getId())) { - throw new ResourceOwnershipException("Only the list owner can view permissions"); - } + ShoppingList list = shoppingListService.findListById(id); + verifyOwnership(list, user, "Only the list owner can view permissions"); return listPermissionRepository.findByShoppingListIdAndDeletedFalse(id); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionService.java` around lines 100 - 108, The method findAllPermissionsByListId currently calls shoppingListService.findAndVerifyAccess and then enforces owner-only access again, which is redundant; fix by either (A) replace the call to findAndVerifyAccess with a simpler existence check such as shoppingListService.findListById (or equivalent findById that verifies existence/deleted) and keep the owner-only check and the ResourceOwnershipException, or (B) keep the call to findAndVerifyAccess and remove the subsequent owner-only check lines (the throw) so ownership isn’t redundantly enforced. Update findAllPermissionsByListId in ListPermissionService and adjust the call to shoppingListService accordingly (use findListById or remove the extra check) so behavior is consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/gemini-dispatch.yml:
- Around line 125-165: The review and invoke jobs reference missing reusable
workflow files causing runtime failures; either add the two workflows named ./
.github/workflows/gemini-review.yml and ./.github/workflows/gemini-invoke.yml
implementing the expected inputs/permissions/outputs used by the review and
invoke jobs, or update the review and invoke job 'uses' values to point to
existing reusable workflows; locate the job definitions named review and invoke
in the gemini-dispatch.yml and ensure the referenced workflow filenames and
required input/secret contracts match the created workflows (also verify triage
uses ./ .github/workflows/gemini-triage.yml if intended).
In @.github/workflows/gemini-triage.yml:
- Around line 23-25: The selected_labels output is empty because
env.SELECTED_LABELS is never set; update the triage job to extract labels from
the Gemini CLI result (e.g., steps.gemini_analysis.outputs.summary or
steps.get_labels.outputs.*) and set a step output or env var that the job
outputs can reference; specifically, add a step after run-gemini-cli that parses
steps.gemini_analysis.outputs.summary into a step output like selected_labels
(or sets an env var SELECTED_LABELS) and then change outputs.selected_labels to
reference that step output (instead of env.SELECTED_LABELS) so the downstream
label job condition can evaluate correctly.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/auth/configs/RsaKeyConfig.java`:
- Around line 26-27: The PEM loading currently calls
privateKeyResource.getInputStream().readAllBytes() (and similarly
publicKeyResource.getInputStream()) without closing the stream and converts
bytes to String using the platform default charset; change both to use
try-with-resources to open the InputStream and readAllBytes inside the try
block, and convert bytes to String with StandardCharsets.US_ASCII (e.g. new
String(bytes, StandardCharsets.US_ASCII)) before stripping "-----BEGIN/END
PRIVATE/ PUBLIC KEY-----" and whitespace so streams are closed deterministically
and parsing uses an explicit charset; update the code paths that build the key
String (the variables computed from privateKeyResource and publicKeyResource)
accordingly.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/auth/service/CustomUserDetailsService.java`:
- Around line 20-21: The current orElseThrow in CustomUserDetailsService returns
a UsernameNotFoundException that echoes the submitted email; change the thrown
message to a generic, non-PII string (e.g., "User not found" or "Invalid
credentials") so the repository lookup in userRepository.findByEmail(email) no
longer leaks the email; update the exception construction in the method (the
lambda passed to orElseThrow) to use the generic message while keeping the same
exception type UsernameNotFoundException.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ListItemController.java`:
- Around line 37-40: The controller currently ignores the path-level listId for
single-item operations; update ListItemController handlers (getListItemById,
createListItem, updateListItem, deleteListItem) to accept `@PathVariable` Long
listId and pass it to service methods (e.g., change calls to
service.findListItemById(id, user) -> service.findListItemById(listId, id, user)
or add new service overloads that accept listId) and validate that
requestDTO.listId() (for POST/PUT) matches the path listId, returning a 400/403
on mismatch; also update the service implementations to enforce the parent
listId check and add a regression test that sends a request with mismatched path
listId vs body listId to ensure the controller rejects it.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ListPermissionController.java`:
- Around line 47-49: Ensure the controller enforces the nested resource by
accepting the path variable listId in ListPermissionController methods (e.g.,
addListPermission and the other permission mutation endpoints) and passing that
listId into the service (e.g., service.addListPermission(..., listId)); inside
the controller validate that requestDTO.idList() (or the target permission's
list id for PUT/DELETE) matches the path listId and reject mismatches with an
appropriate 4xx response, and update the corresponding service signatures to
accept the explicit listId so the service can also enforce/validate parent/child
consistency.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListService.java`:
- Around line 74-76: The editList method currently uses the generic
findAndVerifyAccess(ShoppingList.getId(), currentLoggedUser) which allows
READ-only collaborators to perform mutations; change this to a mutation-specific
authorization check (e.g., findAndVerifyWriteAccess or verifyOwner) before
performing any save or delete operations in editList and the similar blocks at
lines 92-95; update or add a new helper (referenced by name in ListItemService
usage) that asserts WRITE or OWNER permissions and call that helper instead of
the generic read check so only users with write/owner rights can rename lists or
modify items.
In `@backend/src/main/java/com/omatheusmesmo/shoppmate/user/entity/User.java`:
- Around line 15-16: In User.java replace the use of
Collections.singletonList(...) with List.of(...) where authorities are created
(e.g., in the getAuthorities / getRoles logic that currently calls
Collections.singletonList(new SimpleGrantedAuthority(role))); update that
expression to List.of(new SimpleGrantedAuthority(role)) and ensure the import
for java.util.List is present; optionally, if you intend to use role-based
checks later, prepend "ROLE_" to the authority string when constructing
SimpleGrantedAuthority (e.g., "ROLE_" + role) so future hasRole(...) checks work
as expected.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/utils/exception/GlobalExceptionHandler.java`:
- Around line 105-109: The handler handleResourceOwnership currently logs and
returns ex.getMessage(), which may expose PII; change it to avoid returning or
logging raw exception details: update the log.warn call to a generic message
like "Resource ownership violation detected" (optionally log a non-sensitive
error id) and construct the ApiError with a generic message (e.g., "Access
Denied" or "You do not have permission to access this resource") without
including ex.getMessage(); keep ResourceOwnershipException for control flow but
do not propagate its message to logs or the ResponseEntity.
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListPermissionControllerSecurityIntegrationTest.java`:
- Around line 149-156: The test duplicates the "forbidden" scenario by posting
to ownerList with tokenUserA; instead exercise the owner-grant path: in
ListPermissionControllerSecurityIntegrationTest update the request to
authenticate as the list owner (use the owner token variable, e.g., tokenOwner
or whatever represents the owner) when posting maliciousDTO for ownerList and
change the expectation from status().isForbidden() to the successful owner
outcome (e.g., status().isOk() and appropriate json assertions) so the
owner-can-grant-on-their-own-list path is covered.
In `@frontend/src/app/shared/components/list-item/list-item.component.html`:
- Around line 113-116: The template renders the same subtitle twice; remove the
duplicated interpolation block that repeats "{{ listItem.quantity }} {{
listItem.item.unit.symbol }} x {{ listItem.unitPrice | currency }} = {{
listItem.totalPrice | currency }} | Category: {{ listItem.item.category.name }}"
so only the original single instance remains in list-item.component.html
(referencing the listItem template variable within the ListItemComponent).
Delete the redundant block (the second occurrence) and verify screen-reader
output still conveys the same info or add an ARIA attribute on the remaining
element if needed to preserve accessibility.
In `@frontend/src/app/shared/services/shopping-list.service.ts`:
- Around line 12-15: Remove the duplicated import of ListItemRequestDTO and
ListItemResponseDTO in shopping-list.service.ts by deleting the redundant import
block so only the original import remains; ensure the remaining import statement
(the one that imports ListItemRequestDTO and ListItemResponseDTO) is kept and
properly grouped with other imports to avoid TypeScript/lint errors.
---
Outside diff comments:
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/auth/configs/RsaKeyConfig.java`:
- Around line 26-33: In RsaKeyConfig, validate and safely load the RSA keys:
before reading privateKeyResource (and likewise publicKeyResource), check
resource.exists() and resource.isReadable(), and reject or throw if the resource
URL has an untrusted remote scheme (e.g., only allow "file" or "classpath"
schemes) to prevent loading from attacker-controlled URLs; read the stream using
a try-with-resources (or explicitly close the InputStream) and construct the
String with an explicit charset (StandardCharsets.UTF_8) instead of new
String(byte[]), then proceed to Base64-decode and build the PKCS8EncodedKeySpec;
apply the same validation/stream-closing/charset fixes to the public key loading
path as well.
In `@README.md`:
- Around line 74-100: Remove the duplicate and mislabelled sections in the
README by deleting the Maven/backend snippet that is incorrectly under the
"Frontend" heading and removing the repeated npm install/start block; ensure
there is a single correctly labeled "Backend" or "Server" section containing the
Java 17 and ./mvnw spring-boot:run instructions and a single "Frontend" section
that lists Navigate to `frontend/`, npm install, npm start, and the
http://localhost:4200 URL so the README no longer contains duplicated npm
instructions or the misplaced Maven commands.
---
Nitpick comments:
In @.github/workflows/gemini-triage.yml:
- Line 62: The workflow currently references the floating tag
'google-github-actions/run-gemini-cli@v0' (with '# ratchet:exclude') which can
change; update the workflow to pin the action to a specific commit SHA or stable
tag (or add a short justification comment if you must keep the floating tag) so
CI behavior is deterministic; locate the uses line referencing
'google-github-actions/run-gemini-cli@v0' and replace '@v0' with a concrete
'@<commit-sha>' or '@<tag>' (or add an explicit rationale near the '#
ratchet:exclude' comment).
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/item/service/ItemService.java`:
- Around line 55-57: Split the responsibilities of ItemService.findById into a
rich-read method and a lightweight existence check: keep a new read method
(e.g., findByIdWithRelations or keep findById but ensure it uses
itemRepository.findByIdWithRelations(id) and throws NoSuchElementException) for
read paths that need full relations, and add a separate precheck method (e.g.,
ensureExistsById or existsByIdCheck) that calls a lightweight repository lookup
(itemRepository.existsById(id) or itemRepository.findById(id) without loading
relations) and throws the same NoSuchElementException for mutation flows; then
update mutation methods like removeItem and editItem to call the new precheck
method instead of the heavy findByIdWithRelations call so edits/removals don't
incur the extra relation loading.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListController.java`:
- Line 22: Remove the stale TODO comment in ShoppingListController.java; the
functionality is already implemented as getAllShoppingLists now calls
service.findAllByUser(user) which returns only lists owned by or shared with the
authenticated user, so delete the "// TODO: return only lists owned by the user
or that are shared with him" line to avoid confusion.
In
`@backend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionService.java`:
- Around line 22-30: The injected dependencies in ListPermissionService
(listPermissionRepository, shoppingListService, userService, auditService,
listPermissionMapper) are already declared final, so no code changes are needed;
simply confirm the constructor performs proper constructor injection and leave
the final fields as-is.
- Around line 45-48: Extract the repeated ownership check in
ListPermissionService into a private helper method verifyOwnership(ShoppingList
list, User user, String message) that throws ResourceOwnershipException when
list.getOwner().getId().equals(user.getId()) is false; replace the inline checks
in addListPermission, findListUserPermissionById, and findAllPermissionsByListId
with calls to verifyOwnership(shoppingList, requester, "<same message as
original>") to centralize null-safety and error messaging (ensure you call
getOwner() and getId() safely as needed inside the helper).
- Around line 100-108: The method findAllPermissionsByListId currently calls
shoppingListService.findAndVerifyAccess and then enforces owner-only access
again, which is redundant; fix by either (A) replace the call to
findAndVerifyAccess with a simpler existence check such as
shoppingListService.findListById (or equivalent findById that verifies
existence/deleted) and keep the owner-only check and the
ResourceOwnershipException, or (B) keep the call to findAndVerifyAccess and
remove the subsequent owner-only check lines (the throw) so ownership isn’t
redundantly enforced. Update findAllPermissionsByListId in ListPermissionService
and adjust the call to shoppingListService accordingly (use findListById or
remove the extra check) so behavior is consistent.
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/service/ItemServiceTest.java`:
- Around line 109-127: The test ItemServiceTest currently only mocks
itemRepository.findByIdWithRelations(...) so it doesn't exercise the real
repository query or the deleted = false filter; add an integration/repository
test that uses the actual ItemRepository (not Mockito) to persist an Item with
deleted=false and another with deleted=true, then call the real repository
method findByIdWithRelations(id) (or the JPA query backing it) to assert the
deleted=true item is not returned and deleted=false is returned; ensure the test
class boots Spring context or uses `@DataJpaTest` and saves entities via
ItemRepository before invoking findByIdWithRelations to verify the real query
behavior and prevent regressions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e817ed5f-9043-40d2-8db7-7fbd3a03d7fb
📒 Files selected for processing (36)
.github/workflows/gemini-dispatch.yml.github/workflows/gemini-triage.ymlREADME.mdbackend/src/main/java/com/omatheusmesmo/shoppmate/auth/configs/RsaKeyConfig.javabackend/src/main/java/com/omatheusmesmo/shoppmate/auth/service/CustomUserDetailsService.javabackend/src/main/java/com/omatheusmesmo/shoppmate/item/repository/ItemRepository.javabackend/src/main/java/com/omatheusmesmo/shoppmate/item/service/ItemService.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ListItemController.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ListPermissionController.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListController.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/dtos/ShoppingListRequestDTO.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/entity/ShoppingList.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/mapper/ListItemMapper.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/mapper/ListMapper.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/repository/ShoppingListRepository.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListItemService.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionService.javabackend/src/main/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListService.javabackend/src/main/java/com/omatheusmesmo/shoppmate/user/entity/User.javabackend/src/main/java/com/omatheusmesmo/shoppmate/utils/exception/GlobalExceptionHandler.javabackend/src/main/java/com/omatheusmesmo/shoppmate/utils/exception/ResourceOwnershipException.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListItemControllerSecurityIntegrationTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListPermissionControllerSecurityIntegrationTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListControllerSecurityIntegrationTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ListItemServiceTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ListPermissionServiceTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/service/ItemServiceTest.javafrontend/src/app/shared/components/list-item/list-item.component.htmlfrontend/src/app/shared/components/unit/unit.component.tsfrontend/src/app/shared/mocks/mock-services.tsfrontend/src/app/shared/services/category.service.tsfrontend/src/app/shared/services/item.service.tsfrontend/src/app/shared/services/list-permission.service.tsfrontend/src/app/shared/services/shopping-list.service.tsfrontend/src/app/shared/services/unit.service.tsfrontend/src/app/shared/services/user.service.ts
💤 Files with no reviewable changes (2)
- frontend/src/app/shared/mocks/mock-services.ts
- frontend/src/app/shared/components/unit/unit.component.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
backend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListServiceTest.java (2)
95-100: Rename test to match asserted behavior.Line 98 asserts
ResourceOwnershipException, but the method name saysWhenNotFound. Rename for clarity to avoid future confusion in failure triage.Proposed rename
- void testRemoveListThrowsExceptionWhenNotFound() { + void testRemoveListThrowsExceptionWhenUserDoesNotOwnResource() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListServiceTest.java` around lines 95 - 100, Rename the test method testRemoveListThrowsExceptionWhenNotFound to a name that reflects the asserted exception and scenario (e.g., testRemoveListThrowsResourceOwnershipExceptionWhenNotOwnedByUser) so it matches the assertion of ResourceOwnershipException; update the method declaration to the new name and keep the body invoking shoppingListService.removeList(999L, testUser) with the same repository mock (findByIdAndUserId returning Optional.empty()) to preserve behavior.
112-117: Add explicit edit ownership-denied test case.This segment validates only the success path for
editList. Add a negative ownership test to lock in the 403/ownership contract for updates too.Proposed additional test
+ `@Test` + void testEditListThrowsExceptionWhenUserDoesNotOwnResource() { + when(shoppingListRepository.findByIdAndUserId(1L, testUser.getId())) + .thenReturn(Optional.empty()); + + assertThrows(com.omatheusmesmo.shoppmate.utils.exception.ResourceOwnershipException.class, + () -> shoppingListService.editList(testList, testUser)); + + verify(shoppingListRepository, never()).save(any(ShoppingList.class)); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListServiceTest.java` around lines 112 - 117, Add a new negative test alongside testEditListSuccess that verifies ownership is enforced: create a test method (e.g., testEditListOwnershipDenied) that tries to call shoppingListService.editList(testList, testUser) but mock shoppingListRepository.findByIdAndUserId(1L, testUser.getId()) to return Optional.empty() (or otherwise indicate the list does not belong to that user) and assert that editList throws the expected ownership/403 exception (e.g., AccessDeniedException or the service's ResponseStatusException for 403) instead of succeeding; use the same testList and testUser setup and mirror the success test structure but assert the exception is thrown.backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListControllerSecurityIntegrationTest.java (1)
36-61: Refactor DI to constructor injection with immutable fields.Please switch
@Autowiredfields to constructor-injectedfinaldependencies to match repository standards.As per coding guidelines, "Dependency Injection: Always use constructor injection with final fields."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListControllerSecurityIntegrationTest.java` around lines 36 - 61, Replace field injection in ShoppingListControllerSecurityIntegrationTest by making the dependencies private final fields (mockMvc, objectMapper, shoppingListRepository, userRepository, passwordEncoder, jwtService, listMapper, shoppingListService, listPermissionRepository) and injecting them via a constructor; remove the `@Autowired` annotations from the fields, add a single constructor that accepts these types and assigns them to the final fields (you can annotate the constructor with `@Autowired` or omit the annotation if there is only one constructor), and update any usages to reference the final fields.backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListItemControllerSecurityIntegrationTest.java (1)
45-85: Use constructor injection withfinalfields in this test class.Current field injection works, but it violates the project DI rule and makes dependencies mutable.
Refactor sketch
class ListItemControllerSecurityIntegrationTest { - `@Autowired` - private MockMvc mockMvc; + private final MockMvc mockMvc; + private final ObjectMapper objectMapper; + // ...other final dependencies + `@Autowired` + ListItemControllerSecurityIntegrationTest( + MockMvc mockMvc, + ObjectMapper objectMapper, + ListItemRepository listItemRepository, + ShoppingListRepository shoppingListRepository, + ShoppingListService shoppingListService, + ListItemService listItemService, + UserRepository userRepository, + ItemRepository itemRepository, + CategoryRepository categoryRepository, + UnitRepository unitRepository, + PasswordEncoder passwordEncoder, + JwtService jwtService, + ListMapper listMapper, + ListPermissionRepository listPermissionRepository) { + this.mockMvc = mockMvc; + this.objectMapper = objectMapper; + // ...assign all fields + }As per coding guidelines, "Dependency Injection: Always use constructor injection with final fields."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListItemControllerSecurityIntegrationTest.java` around lines 45 - 85, The test class ListItemControllerSecurityIntegrationTest currently uses mutable field injection for many beans (mockMvc, objectMapper, listItemRepository, shoppingListRepository, shoppingListService, listItemService, userRepository, itemRepository, categoryRepository, unitRepository, passwordEncoder, jwtService, listMapper, listPermissionRepository); replace these with constructor injection by declaring each as private final, adding a single `@Autowired` constructor that accepts all those types as parameters and assigns them to the final fields, and remove the individual `@Autowired` annotations from the fields so dependencies are immutable and comply with the project's DI rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListItemControllerSecurityIntegrationTest.java`:
- Around line 30-43: Add the explicit test profile annotation to the test class:
annotate ListItemControllerSecurityIntegrationTest with `@ActiveProfiles`("test")
placed above the class declaration and keep the existing import for
ActiveProfiles; remove any truly unused imports in the file (or other test
classes) to avoid warnings. Ensure the annotation is applied to the class named
ListItemControllerSecurityIntegrationTest so the test uses
application-test.properties and follows the suite convention.
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListControllerSecurityIntegrationTest.java`:
- Around line 23-34: The test class
ShoppingListControllerSecurityIntegrationTest imports ActiveProfiles but doesn't
apply it, so add the `@ActiveProfiles`("test") annotation to the
ShoppingListControllerSecurityIntegrationTest class declaration to ensure the
test profile (application-test.properties, H2 config, test logging) is loaded
when running integration tests; locate the class
ShoppingListControllerSecurityIntegrationTest and annotate it with
`@ActiveProfiles`("test").
---
Nitpick comments:
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListItemControllerSecurityIntegrationTest.java`:
- Around line 45-85: The test class ListItemControllerSecurityIntegrationTest
currently uses mutable field injection for many beans (mockMvc, objectMapper,
listItemRepository, shoppingListRepository, shoppingListService,
listItemService, userRepository, itemRepository, categoryRepository,
unitRepository, passwordEncoder, jwtService, listMapper,
listPermissionRepository); replace these with constructor injection by declaring
each as private final, adding a single `@Autowired` constructor that accepts all
those types as parameters and assigns them to the final fields, and remove the
individual `@Autowired` annotations from the fields so dependencies are immutable
and comply with the project's DI rule.
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListControllerSecurityIntegrationTest.java`:
- Around line 36-61: Replace field injection in
ShoppingListControllerSecurityIntegrationTest by making the dependencies private
final fields (mockMvc, objectMapper, shoppingListRepository, userRepository,
passwordEncoder, jwtService, listMapper, shoppingListService,
listPermissionRepository) and injecting them via a constructor; remove the
`@Autowired` annotations from the fields, add a single constructor that accepts
these types and assigns them to the final fields (you can annotate the
constructor with `@Autowired` or omit the annotation if there is only one
constructor), and update any usages to reference the final fields.
In
`@backend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListServiceTest.java`:
- Around line 95-100: Rename the test method
testRemoveListThrowsExceptionWhenNotFound to a name that reflects the asserted
exception and scenario (e.g.,
testRemoveListThrowsResourceOwnershipExceptionWhenNotOwnedByUser) so it matches
the assertion of ResourceOwnershipException; update the method declaration to
the new name and keep the body invoking shoppingListService.removeList(999L,
testUser) with the same repository mock (findByIdAndUserId returning
Optional.empty()) to preserve behavior.
- Around line 112-117: Add a new negative test alongside testEditListSuccess
that verifies ownership is enforced: create a test method (e.g.,
testEditListOwnershipDenied) that tries to call
shoppingListService.editList(testList, testUser) but mock
shoppingListRepository.findByIdAndUserId(1L, testUser.getId()) to return
Optional.empty() (or otherwise indicate the list does not belong to that user)
and assert that editList throws the expected ownership/403 exception (e.g.,
AccessDeniedException or the service's ResponseStatusException for 403) instead
of succeeding; use the same testList and testUser setup and mirror the success
test structure but assert the exception is thrown.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 05671a8e-7055-47e7-9ddc-85c37b9c7422
📒 Files selected for processing (7)
backend/src/main/java/com/omatheusmesmo/shoppmate/list/entity/ListPermission.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListItemControllerSecurityIntegrationTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListPermissionControllerSecurityIntegrationTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ShoppingListControllerSecurityIntegrationTest.javabackend/src/test/java/com/omatheusmesmo/shoppmate/list/service/ShoppingListServiceTest.javafrontend/src/app/shared/components/unit/unit.component.tsfrontend/src/app/shared/services/shopping-list.service.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- backend/src/test/java/com/omatheusmesmo/shoppmate/list/controller/ListPermissionControllerSecurityIntegrationTest.java
- frontend/src/app/shared/services/shopping-list.service.ts
- frontend/src/app/shared/components/unit/unit.component.ts
|
Hello @felipemelozx ,you can just ignore coderabbit if the file isn't related to your feature. ASAP I'll take a look at your pr. |
2c14753 to
3d5a061
Compare
- ListItemServiceTest: use verifyOwnership instead of findAndVerifyAccess - ShoppingListServiceTest: fix mock for removeList test - ItemServiceTest: use findByIdWithRelations instead of findById - Integration tests: fix expected status codes
omatheusmesmo
left a comment
There was a problem hiding this comment.
@felipemelozx Thanks for working on this — the IDOR fix looks solid, all 127 tests pass and the ownership verification logic is correct. ✅
Before merging, could you please clean up the branch:
- Resolve merge conflicts with the latest develop
- Remove IDE config files — backend/.settings/ and backend/.factorypath should not be committed. Please add them to .gitignore
- Restore
README.md— it was deleted but is not related to this fix
Once that is done, I will merge this right away. 🚀
|
Hey @felipemelozx , Almost there, just check the latest backend CI errors. Thanks, |
ProblemController unit tests were failing because SolutionCreated a custom annotation following Spring Security best practices to properly inject our custom ChangesNew Files
Updated Tests
|
|
Great job @felipemelozx , thanks for your patience! |
Pull Request Description
This PR implements a complete fix for an ID Spoofing (IDOR) vulnerability that allowed authenticated users to access or modify resources belonging to other users by manipulating user IDs in request payloads.
Related Task
Resolves #15
What was done?
@AuthenticationPrincipal, allowing the authenticated user to be automatically injected from theSecurityContextHolder.Userentity to implementUserDetails, enabling Spring Security to manage it directly after authentication through the JWT filter.ResourceOwnershipExceptionis thrown and an HTTP 403 Forbidden response is returned.idUserfield from request DTOs to eliminate the spoofing vector.Tests
How to Test
Useful Links
@AuthenticationPrincipaldocumentationSummary by CodeRabbit
New Features
Documentation
Tests