security hardening #12

Merged
Cedric merged 15 commits from feature/security-hardening into main 2026-05-05 17:13:54 +02:00
3 changed files with 18 additions and 38 deletions
Showing only changes of commit 906b60d264 - Show all commits
@@ -16,11 +16,9 @@ public interface ExpenseListRepository extends JpaRepository<ExpenseList, Long>
ExpenseList findByInviteCode(String inviteCode); ExpenseList findByInviteCode(String inviteCode);
@Query("SELECT el FROM ExpenseList el WHERE el.owner.id = :userId OR el.sharedWith.id = :sharedUserId") @Query("SELECT el FROM ExpenseList el WHERE el.owner.id = :userId OR el.sharedWith.id = :userId")
List<ExpenseList> findByOwnerIdOrSharedWithId(@Param("userId") Long userId, List<ExpenseList> findByOwnerIdOrSharedWithId(@Param("userId") Long userId);
@Param("sharedUserId") Long sharedUserId);
@Query("SELECT el FROM ExpenseList el WHERE el.owner.username = :username OR el.sharedWith.username = :sharedUsername") @Query("SELECT el FROM ExpenseList el WHERE el.owner.username = :username OR el.sharedWith.username = :username")
List<ExpenseList> findByOwnerUsernameOrSharedWithUsername(@Param("username") String username, List<ExpenseList> findByOwnerUsernameOrSharedWithUsername(@Param("username") String username);
@Param("sharedUsername") String sharedUsername);
} }
@@ -1,8 +1,6 @@
package de.zendric.app.xpensely_server.services; package de.zendric.app.xpensely_server.services;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@@ -10,9 +8,9 @@ import java.util.UUID;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.Expense; import de.zendric.app.xpensely_server.model.Expense;
import de.zendric.app.xpensely_server.model.ExpenseList; import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.model.Exception.ResourceNotFoundException;
import de.zendric.app.xpensely_server.model.XpenselyCustomCategory; import de.zendric.app.xpensely_server.model.XpenselyCustomCategory;
import de.zendric.app.xpensely_server.repo.ExpenseListRepository; import de.zendric.app.xpensely_server.repo.ExpenseListRepository;
import de.zendric.app.xpensely_server.repo.ExpenseRepository; import de.zendric.app.xpensely_server.repo.ExpenseRepository;
@@ -62,40 +60,24 @@ public class ExpenseListService {
} }
public List<ExpenseList> findByUserId(Long id) { public List<ExpenseList> findByUserId(Long id) {
return repository.findByOwnerIdOrSharedWithId(id, id); return repository.findByOwnerIdOrSharedWithId(id);
} }
public List<ExpenseList> findByUsername(String username) { public List<ExpenseList> findByUsername(String username) {
return repository.findByOwnerUsernameOrSharedWithUsername(username, username); return repository.findByOwnerUsernameOrSharedWithUsername(username);
} }
public Expense addExpenseToList(Long expenseListId, Expense expense) { public Expense addExpenseToList(Long expenseListId, Expense expense) {
// find expenseList
ExpenseList expenseList = repository.findById(expenseListId) ExpenseList expenseList = repository.findById(expenseListId)
.orElseThrow(() -> new RuntimeException("ExpenseList not found with id: " + expenseListId)); .orElseThrow(() -> new ResourceNotFoundException("ExpenseList not found with id: " + expenseListId));
// get all added expenses
HashSet<Long> existingId = new HashSet<>();
for (Expense e : expenseList.getExpenses()) {
existingId.add(e.getId());
}
// add the new expense
expenseList.addExpense(expense); expenseList.addExpense(expense);
// save
repository.save(expenseList); repository.save(expenseList);
return expense;
Expense newExpense = new Expense();
for (Expense e : expenseList.getExpenses()) {
if (!existingId.contains(e.getId())) {
newExpense = e;
break;
}
}
return newExpense;
} }
public void deleteExpenseFromList(Long expenseListId, Long expenseId) { public void deleteExpenseFromList(Long expenseListId, Long expenseId) {
ExpenseList expenseList = repository.findById(expenseListId) ExpenseList expenseList = repository.findById(expenseListId)
.orElseThrow(() -> new RuntimeException("ExpenseList not found with id: " + expenseListId)); .orElseThrow(() -> new ResourceNotFoundException("ExpenseList not found with id: " + expenseListId));
Expense expenseToRemove = null; Expense expenseToRemove = null;
for (Expense expense : expenseList.getExpenses()) { for (Expense expense : expenseList.getExpenses()) {
if (expense.getId().equals(expenseId)) { if (expense.getId().equals(expenseId)) {
@@ -106,14 +88,14 @@ public class ExpenseListService {
if (expenseToRemove != null) { if (expenseToRemove != null) {
expenseList.removeExpense(expenseToRemove); expenseList.removeExpense(expenseToRemove);
} else { } else {
throw new RuntimeException("Expense not found with id: " + expenseId); throw new ResourceNotFoundException("Expense not found with id: " + expenseId);
} }
repository.save(expenseList); repository.save(expenseList);
} }
public String generateInviteCode(Long listId) { public String generateInviteCode(Long listId) {
ExpenseList list = repository.findById(listId) ExpenseList list = repository.findById(listId)
.orElseThrow(() -> new RuntimeException("List not found")); .orElseThrow(() -> new ResourceNotFoundException("List not found"));
String inviteCode; String inviteCode;
if (list.getInviteCode() == null || list.getInviteCodeExpiration().isBefore(LocalDateTime.now())) { if (list.getInviteCode() == null || list.getInviteCodeExpiration().isBefore(LocalDateTime.now())) {
@@ -158,7 +140,7 @@ public class ExpenseListService {
// TODO implement API for this // TODO implement API for this
public XpenselyCustomCategory addCustomCategory(Long expenseListId, XpenselyCustomCategory customCategory) { public XpenselyCustomCategory addCustomCategory(Long expenseListId, XpenselyCustomCategory customCategory) {
ExpenseList expenseList = repository.findById(expenseListId) ExpenseList expenseList = repository.findById(expenseListId)
.orElseThrow(() -> new RuntimeException("Expense List not found")); .orElseThrow(() -> new ResourceNotFoundException("Expense List not found"));
customCategory.setExpenseList(expenseList); customCategory.setExpenseList(expenseList);
return customCategoryRepository.save(customCategory); return customCategoryRepository.save(customCategory);
@@ -167,7 +149,7 @@ public class ExpenseListService {
// TODO implement API for this // TODO implement API for this
public void deleteCustomCategory(Long expenseListId, Long categoryId) { public void deleteCustomCategory(Long expenseListId, Long categoryId) {
XpenselyCustomCategory category = customCategoryRepository.findById(categoryId) XpenselyCustomCategory category = customCategoryRepository.findById(categoryId)
.orElseThrow(() -> new RuntimeException("Custom Category not found")); .orElseThrow(() -> new ResourceNotFoundException("Custom Category not found"));
if (!category.getExpenseList().getId().equals(expenseListId)) { if (!category.getExpenseList().getId().equals(expenseListId)) {
throw new RuntimeException("Category does not belong to the specified Expense List"); throw new RuntimeException("Category does not belong to the specified Expense List");
} }
@@ -33,12 +33,12 @@ class ExpenseListServiceTest {
void findByUserId_usesRepositoryQuery_notFindAll() { void findByUserId_usesRepositoryQuery_notFindAll() {
AppUser owner = new AppUser(); owner.setId(1L); AppUser owner = new AppUser(); owner.setId(1L);
ExpenseList list = new ExpenseList(); list.setId(10L); list.setOwner(owner); ExpenseList list = new ExpenseList(); list.setId(10L); list.setOwner(owner);
when(repository.findByOwnerIdOrSharedWithId(1L, 1L)).thenReturn(List.of(list)); when(repository.findByOwnerIdOrSharedWithId(1L)).thenReturn(List.of(list));
List<ExpenseList> result = service.findByUserId(1L); List<ExpenseList> result = service.findByUserId(1L);
assertThat(result).hasSize(1); assertThat(result).hasSize(1);
verify(repository).findByOwnerIdOrSharedWithId(1L, 1L); verify(repository).findByOwnerIdOrSharedWithId(1L);
verify(repository, never()).findAll(); verify(repository, never()).findAll();
} }
@@ -46,12 +46,12 @@ class ExpenseListServiceTest {
void findByUsername_usesRepositoryQuery_notFindAll() { void findByUsername_usesRepositoryQuery_notFindAll() {
AppUser owner = new AppUser(); owner.setId(1L); owner.setUsername("alice"); AppUser owner = new AppUser(); owner.setId(1L); owner.setUsername("alice");
ExpenseList list = new ExpenseList(); list.setId(10L); list.setOwner(owner); ExpenseList list = new ExpenseList(); list.setId(10L); list.setOwner(owner);
when(repository.findByOwnerUsernameOrSharedWithUsername("alice", "alice")).thenReturn(List.of(list)); when(repository.findByOwnerUsernameOrSharedWithUsername("alice")).thenReturn(List.of(list));
List<ExpenseList> result = service.findByUsername("alice"); List<ExpenseList> result = service.findByUsername("alice");
assertThat(result).hasSize(1); assertThat(result).hasSize(1);
verify(repository).findByOwnerUsernameOrSharedWithUsername("alice", "alice"); verify(repository).findByOwnerUsernameOrSharedWithUsername("alice");
verify(repository, never()).findAll(); verify(repository, never()).findAll();
} }
} }