fix: Added constraints for valid usernames
authorKai Moritz <kai@juplo.de>
Tue, 10 Jan 2023 22:59:36 +0000 (23:59 +0100)
committerKai Moritz <kai@juplo.de>
Wed, 25 Jan 2023 20:59:37 +0000 (21:59 +0100)
- This fix is necessary, because not all usernames can be stored in
  the newly introduced `MongoDbStorageStrategy`.

src/main/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerAdvice.java
src/main/java/de/juplo/kafka/chat/backend/domain/ChatRoom.java
src/main/java/de/juplo/kafka/chat/backend/domain/InvalidUsernameException.java [new file with mode: 0644]
src/test/java/de/juplo/kafka/chat/backend/api/ChatBackendControllerTest.java

index fa3a0e1..55350f1 100644 (file)
@@ -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;
+  }
 }
index 35d0c3d..3019743 100644 (file)
@@ -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 (file)
index 0000000..f9a9b12
--- /dev/null
@@ -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;
+  }
+}
index 2f120ae..eeaf5ee 100644 (file)
@@ -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));
+  }
 }