From: Kai Moritz Date: Tue, 10 Jan 2023 22:59:36 +0000 (+0100) Subject: fix: Added constraints for valid usernames X-Git-Tag: wip-config-package~8 X-Git-Url: http://juplo.de/gitweb/?a=commitdiff_plain;h=3b2e7d20030784a50dca31fd64b4c057468c43b7;p=demos%2Fkafka%2Fchat fix: Added constraints for valid usernames - This fix is necessary, because not all usernames can be stored in the newly introduced `MongoDbStorageStrategy`. --- diff --git a/src/main/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerAdvice.java b/src/main/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerAdvice.java index fa3a0e16..55350f17 100644 --- a/src/main/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerAdvice.java +++ b/src/main/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerAdvice.java @@ -1,5 +1,6 @@ package de.juplo.kafka.chat.backend.api; +import de.juplo.kafka.chat.backend.domain.InvalidUsernameException; import de.juplo.kafka.chat.backend.domain.MessageMutationException; import de.juplo.kafka.chat.backend.domain.UnknownChatroomException; import org.springframework.beans.factory.annotation.Value; @@ -83,4 +84,37 @@ public class ChatBackendControllerAdvice return problem; } + + @ExceptionHandler(InvalidUsernameException.class) + public final ProblemDetail handleException( + InvalidUsernameException e, + ServerWebExchange exchange, + UriComponentsBuilder uriComponentsBuilder) + { + final HttpStatus status = HttpStatus.BAD_REQUEST; + ProblemDetail problem = ProblemDetail.forStatus(status); + + problem.setProperty("timestamp", new Date()); + + problem.setProperty("requestId", exchange.getRequest().getId()); + + problem.setType(uriComponentsBuilder.replacePath(contextPath).path("/problem/invalid-username").build().toUri()); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(status.getReasonPhrase()); + stringBuilder.append(" - "); + stringBuilder.append(e.getMessage()); + problem.setTitle(stringBuilder.toString()); + + stringBuilder.setLength(0); + stringBuilder.append("Invalid username: "); + stringBuilder.append(e.getUsername()); + stringBuilder.append( + "! A valid username must consist of at at least two letters and " + + "must only contain lower case letters a-z, numbers and dashes"); + problem.setDetail(stringBuilder.toString()); + + problem.setProperty("username", e.getUsername()); + + return problem; + } } diff --git a/src/main/java/de/juplo/kafka/chat/backend/domain/ChatRoom.java b/src/main/java/de/juplo/kafka/chat/backend/domain/ChatRoom.java index 35d0c3d4..30197432 100644 --- a/src/main/java/de/juplo/kafka/chat/backend/domain/ChatRoom.java +++ b/src/main/java/de/juplo/kafka/chat/backend/domain/ChatRoom.java @@ -11,6 +11,8 @@ import reactor.core.publisher.Sinks; import java.time.Clock; import java.time.LocalDateTime; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Slf4j @@ -18,6 +20,7 @@ import java.util.*; @ToString(of = { "id", "name" }) public class ChatRoom { + public final static Pattern VALID_USER = Pattern.compile("^[a-z0-9-]{2,}$"); @Getter private final UUID id; @Getter @@ -48,6 +51,10 @@ public class ChatRoom String user, String text) { + Matcher matcher = VALID_USER.matcher(user); + if (!matcher.matches()) + throw new InvalidUsernameException(user); + Message.MessageKey key = Message.MessageKey.of(user, id); return service .getMessage(key) diff --git a/src/main/java/de/juplo/kafka/chat/backend/domain/InvalidUsernameException.java b/src/main/java/de/juplo/kafka/chat/backend/domain/InvalidUsernameException.java new file mode 100644 index 00000000..f9a9b122 --- /dev/null +++ b/src/main/java/de/juplo/kafka/chat/backend/domain/InvalidUsernameException.java @@ -0,0 +1,16 @@ +package de.juplo.kafka.chat.backend.domain; + +import lombok.Getter; + + +public class InvalidUsernameException extends RuntimeException +{ + @Getter + private final String username; + + public InvalidUsernameException(String username) + { + super("Invalid username: " + username); + this.username = username; + } +} diff --git a/src/test/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerTest.java b/src/test/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerTest.java index 2f120ae2..eeaf5ee8 100644 --- a/src/test/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerTest.java +++ b/src/test/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerTest.java @@ -204,4 +204,46 @@ public class ChatBackendControllerTest .jsonPath("$.mutatedText").isEqualTo(textMutatedMessage); verify(chatRoomService, never()).persistMessage(eq(key), any(LocalDateTime.class), any(String.class)); } + + @Test + @DisplayName("Assert expected problem-details for invalid username on PUT /put/{chatroomId}/{username}/{messageId}") + void testInvalidUsernameException(@Autowired WebTestClient client) throws Exception + { + // Given + UUID chatroomId = UUID.randomUUID(); + String user = "Foo"; + Long messageId = 66l; + Message.MessageKey key = Message.MessageKey.of(user, messageId); + String textMessage = "Hallo Welt"; + ChatRoom chatRoom = new ChatRoom( + chatroomId, + "Test-ChatRoom", + Clock.systemDefaultZone(), + chatRoomService, 8); + when(chatHomeService.getChatRoom(any(UUID.class))) + .thenReturn(Mono.just(chatRoom)); + when(chatRoomService.getMessage(any(Message.MessageKey.class))) + .thenReturn(Mono.empty()); + // Needed for readable error-reports, in case of a bug that leads to according unwanted call + when(chatRoomService.persistMessage(any(Message.MessageKey.class), any(LocalDateTime.class), any(String.class))) + .thenReturn(mock(Message.class)); + + // When + client + .put() + .uri( + "/put/{chatroomId}/{username}/{messageId}", + chatroomId, + user, + messageId) + .bodyValue(textMessage) + .accept(MediaType.APPLICATION_JSON) + .exchange() + // Then + .expectStatus().is4xxClientError() + .expectBody() + .jsonPath("$.type").isEqualTo("/problem/invalid-username") + .jsonPath("$.username").isEqualTo(user); + verify(chatRoomService, never()).persistMessage(eq(key), any(LocalDateTime.class), any(String.class)); + } }