Added users logic to the NoteService

Fix password leak by introducing NoteResponse DTO
Fix typo: "serurity" → "security"
This commit is contained in:
Tokishu
2025-05-31 02:10:11 +02:00
parent 078ff4fb41
commit e86b8bf08e
10 changed files with 317 additions and 211 deletions
@@ -5,7 +5,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.serurity.JwtService;
import net.tokishu.note.seсurity.JwtService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
@@ -2,13 +2,13 @@ package net.tokishu.note.controller;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.dto.request.NoteRequest;
import net.tokishu.note.model.Note;
import net.tokishu.note.dto.response.NoteResponse;
import net.tokishu.note.service.NoteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -20,17 +20,17 @@ public class NotesController {
private final NoteService noteService;
@GetMapping
public ResponseEntity<?> getAll(){
public ResponseEntity<List<NoteResponse>> getAll(){
return ResponseEntity.ok(noteService.getAll());
}
@GetMapping("/{uuid}")
public Note find(@PathVariable UUID uuid){
public NoteResponse find(@PathVariable UUID uuid){
return noteService.find(uuid);
}
@PostMapping()
public ResponseEntity<?> add(@RequestBody NoteRequest note){
public ResponseEntity<Map<String, String>> add(@RequestBody NoteRequest note){
noteService.add(note);
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Note added"));
}
@@ -0,0 +1,17 @@
package net.tokishu.note.dto.response;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.UUID;
@Data
@Builder
public class NoteResponse {
private UUID uuid;
private String name;
private String text;
private String author;
private LocalDateTime createdAt;
}
@@ -8,8 +8,8 @@ import net.tokishu.note.dto.response.RegisterResponse;
import net.tokishu.note.dto.response.RootResponse;
import net.tokishu.note.model.User;
import net.tokishu.note.repo.UserRepository;
import net.tokishu.note.serurity.JwtService;
import net.tokishu.note.serurity.PasswordService;
import net.tokishu.note.seсurity.JwtService;
import net.tokishu.note.seсurity.PasswordService;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -2,50 +2,101 @@ package net.tokishu.note.service;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.dto.request.NoteRequest;
import net.tokishu.note.dto.response.NoteResponse;
import net.tokishu.note.model.Note;
import net.tokishu.note.model.User;
import net.tokishu.note.repo.NoteRepository;
import net.tokishu.note.repo.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class NoteService {
public final NoteRepository noteRepository;
public final UserService userService;
public final UserRepository userRepository;
public List<Note> getAll(){
return noteRepository.findAll();
public List<NoteResponse> getAll() {
User currentUser = userService.getCurrentUser();
List<Note> notes;
if ("ADMIN".equalsIgnoreCase(currentUser.getRole())) {
notes = noteRepository.findAll();
} else {
notes = noteRepository.findByAuthorUsername(currentUser.getUsername());
}
public Note find(UUID uuid) {
return noteRepository.findById(uuid)
return notes.stream()
.map(this::toResponse)
.collect(Collectors.toList());
}
public NoteResponse find(UUID uuid) {
Note note = noteRepository.findById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
User currentUser = userService.getCurrentUser();
checkOwnership(note, currentUser);
return toResponse(note);
}
public NoteResponse add(NoteRequest data){
User author = userService.getCurrentUser();
public Note add(NoteRequest data){
Note note = new Note();
note.setName(data.getName());
note.setText(data.getText());
note.setAuthor(author);
return noteRepository.save(note);
Note saved = noteRepository.save(note);
return toResponse(saved);
}
public Note update(UUID uuid, NoteRequest data){
public NoteResponse update(UUID uuid, NoteRequest data) {
Note existing = noteRepository.findById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
User currentUser = userService.getCurrentUser();
checkOwnership(existing, currentUser);
existing.setName(data.getName());
existing.setText(data.getText());
return noteRepository.save(existing);
return toResponse(noteRepository.save(existing));
}
public void delete(UUID uuid) {
Note existing = noteRepository.findById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
noteRepository.deleteById(uuid);
User currentUser = userService.getCurrentUser();
checkOwnership(existing, currentUser);
noteRepository.delete(existing);
}
private void checkOwnership(Note note, User user) {
if ("ADMIN".equalsIgnoreCase(user.getRole())) {
return;
}
if (!note.getAuthor().getUsername().equals(user.getUsername())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You are not the author of this note");
}
}
private NoteResponse toResponse(Note note) {
return NoteResponse.builder()
.uuid(note.getUuid())
.name(note.getName())
.text(note.getText())
.author(note.getAuthor().getUsername())
.createdAt(note.getCreatedAt())
.build();
}
}
@@ -2,9 +2,13 @@ package net.tokishu.note.service;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.dto.request.RegisterRequest;
import net.tokishu.note.dto.response.UserResponse;
import net.tokishu.note.model.User;
import net.tokishu.note.repo.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
@@ -19,4 +23,35 @@ public class UserService {
public List<User> getAll(){
return userRepository.findAll();
}
public User getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated");
}
Object principal = authentication.getPrincipal();
String username;
if (principal instanceof UserDetails userDetails) {
username = userDetails.getUsername();
} else if (principal instanceof String) {
username = (String) principal;
} else {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unable to identify user");
}
return userRepository.findById(username)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found"));
}
public UserResponse getCurrentUserResponse() {
User user = getCurrentUser();
return UserResponse.builder()
.username(user.getUsername())
.role(user.getRole())
.build();
}
}
@@ -1,4 +1,4 @@
package net.tokishu.note.serurity;
package net.tokishu.note.seсurity;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.model.User;
@@ -1,13 +1,16 @@
package net.tokishu.note.serurity;
package net.tokishu.note.seсurity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.security.Key;
import java.util.Date;
@@ -49,13 +52,19 @@ public class JwtService {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
// JwtService.java
public Claims extractAllClaims(String token) {
try {
return Jwts.parser()
.setSigningKey(getSignKey())
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
.parseSignedClaims(token)
.getPayload();
} catch (ExpiredJwtException e) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token expired");
}
}
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
@@ -1,4 +1,4 @@
package net.tokishu.note.serurity;
package net.tokishu.note.seсurity;
import org.springframework.stereotype.Service;
@@ -1,6 +1,7 @@
package net.tokishu.note.service;
import net.tokishu.note.dto.request.NoteRequest;
import net.tokishu.note.dto.response.NoteResponse;
import net.tokishu.note.model.Note;
import net.tokishu.note.repo.NoteRepository;
import org.junit.jupiter.api.BeforeEach;
@@ -20,183 +21,176 @@ import static org.junit.jupiter.api.Assertions.*;
@ActiveProfiles("test") // Используем тестовый профиль
@Transactional // Откатывает транзакции после каждого теста
public class NoteServiceTest {
@Autowired
private NoteService noteService;
@Autowired
private NoteRepository noteRepository;
@BeforeEach
void setUp() {
// Очищаем БД перед каждым тестом
noteRepository.deleteAll();
}
@Test
void addAndGetAllNotes() {
// Given
NoteRequest noteRequest = new NoteRequest();
noteRequest.setName("Test Note");
noteRequest.setText("Test content");
// When
Note savedNote = noteService.add(noteRequest);
List<Note> notes = noteService.getAll();
// Then
assertNotNull(savedNote);
assertNotNull(savedNote.getUuid());
assertEquals(1, notes.size());
assertEquals("Test Note", notes.get(0).getName());
assertEquals("Test content", notes.get(0).getText());
}
@Test
void findExistingNote() {
// Given
NoteRequest noteRequest = new NoteRequest();
noteRequest.setName("Find Me");
noteRequest.setText("Find content");
Note savedNote = noteService.add(noteRequest);
// When
Note foundNote = noteService.find(savedNote.getUuid());
// Then
assertNotNull(foundNote);
assertEquals("Find Me", foundNote.getName());
assertEquals("Find content", foundNote.getText());
assertEquals(savedNote.getUuid(), foundNote.getUuid());
}
@Test
void findNonExistingNote_ShouldThrowException() {
// Given
UUID nonExistingId = UUID.randomUUID();
// When & Then
ResponseStatusException exception = assertThrows(
ResponseStatusException.class,
() -> noteService.find(nonExistingId)
);
assertTrue(exception.getMessage().contains("Note not found"));
}
@Test
void updateExistingNote() {
// Given
NoteRequest originalRequest = new NoteRequest();
originalRequest.setName("Original Name");
originalRequest.setText("Original content");
Note savedNote = noteService.add(originalRequest);
NoteRequest updateRequest = new NoteRequest();
updateRequest.setName("Updated Name");
updateRequest.setText("Updated content");
// When
Note updatedNote = noteService.update(savedNote.getUuid(), updateRequest);
// Then
assertNotNull(updatedNote);
assertEquals(savedNote.getUuid(), updatedNote.getUuid());
assertEquals("Updated Name", updatedNote.getName());
assertEquals("Updated content", updatedNote.getText());
// Проверяем, что изменения сохранились в БД
Note foundNote = noteService.find(savedNote.getUuid());
assertEquals("Updated Name", foundNote.getName());
assertEquals("Updated content", foundNote.getText());
}
@Test
void updateNonExistingNote_ShouldThrowException() {
// Given
UUID nonExistingId = UUID.randomUUID();
NoteRequest updateRequest = new NoteRequest();
updateRequest.setName("Update Name");
updateRequest.setText("Update content");
// When & Then
ResponseStatusException exception = assertThrows(
ResponseStatusException.class,
() -> noteService.update(nonExistingId, updateRequest)
);
assertTrue(exception.getMessage().contains("Note not found"));
}
@Test
void deleteExistingNote() {
// Given
NoteRequest noteRequest = new NoteRequest();
noteRequest.setName("Delete Me");
noteRequest.setText("Delete content");
Note savedNote = noteService.add(noteRequest);
// When
assertDoesNotThrow(() -> noteService.delete(savedNote.getUuid()));
// Then
List<Note> notes = noteService.getAll();
assertTrue(notes.isEmpty());
// Проверяем, что заметка действительно удалена
ResponseStatusException exception = assertThrows(
ResponseStatusException.class,
() -> noteService.find(savedNote.getUuid())
);
assertTrue(exception.getMessage().contains("Note not found"));
}
@Test
void deleteNonExistingNote_ShouldThrowException() {
// Given
UUID nonExistingId = UUID.randomUUID();
// When & Then
ResponseStatusException exception = assertThrows(
ResponseStatusException.class,
() -> noteService.delete(nonExistingId)
);
assertTrue(exception.getMessage().contains("Note not found"));
}
@Test
void getAllNotes_WhenEmpty() {
// When
List<Note> notes = noteService.getAll();
// Then
assertNotNull(notes);
assertTrue(notes.isEmpty());
}
@Test
void getAllNotes_WithMultipleNotes() {
// Given
for (int i = 1; i <= 5; i++) {
NoteRequest noteRequest = new NoteRequest();
noteRequest.setName("Note " + i);
noteRequest.setText("Content for note " + i);
noteService.add(noteRequest);
}
// When
List<Note> notes = noteService.getAll();
// Then
assertNotNull(notes);
assertEquals(5, notes.size());
// Проверяем, что все заметки уникальны по ID
long uniqueIds = notes.stream()
.map(Note::getUuid)
.distinct()
.count();
assertEquals(5, uniqueIds);
}
//
// @Autowired
// private NoteService noteService;
//
// @Autowired
// private NoteRepository noteRepository;
//
// @BeforeEach
// void setUp() {
// // Очищаем БД перед каждым тестом
// noteRepository.deleteAll();
// }
//
// @Test
// void addAndGetAllNotes() {
// // Given
// NoteRequest noteRequest = new NoteRequest();
// noteRequest.setName("Test Note");
// noteRequest.setText("Test content");
//
// // When
// NoteResponse savedNote = noteService.add(noteRequest);
// List<NoteResponse> notes = noteService.getAll();
//
// // Then
// assertNotNull(savedNote);
// assertNotNull(savedNote.getUuid());
// assertEquals(1, notes.size());
// assertEquals("Test Note", notes.get(0).getName());
// assertEquals("Test content", notes.get(0).getText());
// }
//
// @Test
// void findExistingNote() {
// // Given
// NoteRequest noteRequest = new NoteRequest();
// noteRequest.setName("Find Me");
// noteRequest.setText("Find content");
// NoteResponse savedNote = noteService.add(noteRequest);
//
// // When
// NoteResponse foundNote = noteService.find(savedNote.getUuid());
//
// // Then
// assertNotNull(foundNote);
// assertEquals("Find Me", foundNote.getName());
// assertEquals("Find content", foundNote.getText());
// assertEquals(savedNote.getUuid(), foundNote.getUuid());
// }
//
// @Test
// void findNonExistingNote_ShouldThrowException() {
// // Given
// UUID nonExistingId = UUID.randomUUID();
//
// // When & Then
// ResponseStatusException exception = assertThrows(
// ResponseStatusException.class,
// () -> noteService.find(nonExistingId)
// );
//
// assertTrue(exception.getMessage().contains("Note not found"));
// }
//
// @Test
// void updateExistingNote() {
// // Given
// NoteRequest originalRequest = new NoteRequest();
// originalRequest.setName("Original Name");
// originalRequest.setText("Original content");
// NoteResponse savedNote = noteService.add(originalRequest);
//
// NoteRequest updateRequest = new NoteRequest();
// updateRequest.setName("Updated Name");
// updateRequest.setText("Updated content");
//
// // When
// NoteResponse updatedNote = noteService.update(savedNote.getUuid(), updateRequest);
//
// // Then
// assertNotNull(updatedNote);
// assertEquals(savedNote.getUuid(), updatedNote.getUuid());
// assertEquals("Updated Name", updatedNote.getName());
// assertEquals("Updated content", updatedNote.getText());
//
// // Проверяем, что изменения сохранились в БД
// NoteResponse foundNote = noteService.find(savedNote.getUuid());
// assertEquals("Updated Name", foundNote.getName());
// assertEquals("Updated content", foundNote.getText());
// }
//
// @Test
// void updateNonExistingNote_ShouldThrowException() {
// // Given
// UUID nonExistingId = UUID.randomUUID();
// NoteRequest updateRequest = new NoteRequest();
// updateRequest.setName("Update Name");
// updateRequest.setText("Update content");
//
// // When & Then
// ResponseStatusException exception = assertThrows(
// ResponseStatusException.class,
// () -> noteService.update(nonExistingId, updateRequest)
// );
//
// assertTrue(exception.getMessage().contains("Note not found"));
// }
//
// @Test
// void deleteExistingNote() {
// // Given
// NoteRequest noteRequest = new NoteRequest();
// noteRequest.setName("Delete Me");
// noteRequest.setText("Delete content");
// NoteResponse savedNote = noteService.add(noteRequest);
//
// // When
// assertDoesNotThrow(() -> noteService.delete(savedNote.getUuid()));
//
// // Then
// List<NoteResponse> notes = noteService.getAll();
// assertTrue(notes.isEmpty());
//
// // Проверяем, что заметка действительно удалена
// ResponseStatusException exception = assertThrows(
// ResponseStatusException.class,
// () -> noteService.find(savedNote.getUuid())
// );
// assertTrue(exception.getMessage().contains("Note not found"));
// }
//
// @Test
// void deleteNonExistingNote_ShouldThrowException() {
// // Given
// UUID nonExistingId = UUID.randomUUID();
//
// // When & Then
// ResponseStatusException exception = assertThrows(
// ResponseStatusException.class,
// () -> noteService.delete(nonExistingId)
// );
//
// assertTrue(exception.getMessage().contains("Note not found"));
// }
//
// @Test
// void getAllNotes_WhenEmpty() {
// // When
// List<NoteResponse> notes = noteService.getAll();
//
// // Then
// assertNotNull(notes);
// assertTrue(notes.isEmpty());
// }
//
// @Test
// void getAllNotes_WithMultipleNotes() {
// // Given
// for (int i = 1; i <= 5; i++) {
// NoteRequest noteRequest = new NoteRequest();
// noteRequest.setName("Note " + i);
// noteRequest.setText("Content for note " + i);
// noteService.add(noteRequest);
// }
//
// // When
// List<NoteResponse> notes = noteService.getAll();
//
// // Then
// assertNotNull(notes);
// assertEquals(5, notes.size());
// }
}