Implemented an interceptor, that forbiddes access according to a header
authorKai Moritz <kai@juplo.de>
Sun, 9 Apr 2023 18:41:49 +0000 (20:41 +0200)
committerKai Moritz <kai@juplo.de>
Mon, 10 Apr 2023 15:45:02 +0000 (17:45 +0200)
src/main/java/de/juplo/demo/BackendVersionInterceptor.java [deleted file]
src/main/java/de/juplo/demo/DemoApplication.java
src/main/java/de/juplo/demo/DemoRestController.java
src/main/java/de/juplo/demo/DemoWebMvcConfigurer.java
src/main/java/de/juplo/demo/GreetingTO.java [deleted file]
src/main/java/de/juplo/demo/MessageTo.java [new file with mode: 0644]
src/main/java/de/juplo/demo/RequestTypeInterceptor.java [new file with mode: 0644]
src/main/java/de/juplo/demo/RequestableByHumans.java [new file with mode: 0644]
src/test/java/de/juplo/demo/DemoApplicationTests.java

diff --git a/src/main/java/de/juplo/demo/BackendVersionInterceptor.java b/src/main/java/de/juplo/demo/BackendVersionInterceptor.java
deleted file mode 100644 (file)
index f8a046b..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package de.juplo.demo;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.servlet.HandlerInterceptor;
-
-import java.io.IOException;
-
-
-@RequiredArgsConstructor
-public class BackendVersionInterceptor implements HandlerInterceptor
-{
-  public final static String BACKEND_VERSION = "X-Backend-Version";
-
-
-  private final String backendVersion;
-
-
-  @Override
-  public boolean preHandle(
-      HttpServletRequest request,
-      HttpServletResponse response,
-      Object handler) throws IOException
-  {
-    String requstedVersion = request.getHeader(BACKEND_VERSION);
-    if (requstedVersion != null && !requstedVersion.equals(backendVersion))
-    {
-      response.sendError(HttpStatus.GONE.value());
-      return false;
-    }
-    else
-    {
-      return true;
-    }
-  }
-}
index 64e04c8..c593c19 100644 (file)
@@ -15,13 +15,6 @@ public class DemoApplication
        String projectVersion;
 
 
-       @Bean
-       public BackendVersionInterceptor backendVersionInterceptor(
-                       @Value("${build.version}") String projectVersion)
-       {
-               return new BackendVersionInterceptor(projectVersion);
-       }
-
        @Bean
        public String from(
                        @Value("${application.name}") String applicationName,
index 4259a30..ed364a0 100644 (file)
@@ -17,8 +17,9 @@ public class DemoRestController
   private final String from;
 
 
-  @PostMapping("{to}")
-  public GreetingTO greet(
+  @RequestableByHumans
+  @PostMapping("/greet/{to}")
+  public MessageTo greet(
       @PathVariable String to,
       @RequestBody String greeting)
   {
@@ -29,6 +30,21 @@ public class DemoRestController
 
     log.info("Greeting from {} to {}: {}", from, to, message);
 
-    return GreetingTO.of(message, from, to);
+    return MessageTo.of(message, from, to);
+  }
+
+  @PostMapping("/acknowledge/{to}")
+  public MessageTo acknowledge(
+      @PathVariable String to,
+      @RequestBody String acknowledgment)
+  {
+    String message = acknowledgment
+        .replaceAll(PLACEHOLDER_FROM, from)
+        .replaceAll(PLACEHOLDER_TO, to)
+        .trim();
+
+    log.info("Acknowledgement from {} to {}: {}", from, to, message);
+
+    return MessageTo.of(message, from, to);
   }
 }
index 35fa5aa..aae2562 100644 (file)
@@ -10,12 +10,12 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 public class DemoWebMvcConfigurer implements WebMvcConfigurer
 {
   @Autowired
-  BackendVersionInterceptor backendVersionInterceptor;
+  RequestTypeInterceptor requestTypeInterceptor;
 
 
   @Override
   public void addInterceptors(InterceptorRegistry registry)
   {
-    registry.addInterceptor(backendVersionInterceptor);
+    registry.addInterceptor(requestTypeInterceptor);
   }
 }
diff --git a/src/main/java/de/juplo/demo/GreetingTO.java b/src/main/java/de/juplo/demo/GreetingTO.java
deleted file mode 100644 (file)
index c61a30e..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.juplo.demo;
-
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-
-import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
-
-
-@Data
-@AllArgsConstructor(staticName = "of")
-@JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(NON_EMPTY)
-public class GreetingTO
-{
-  private String greeting;
-  private String from;
-  private String to;
-}
diff --git a/src/main/java/de/juplo/demo/MessageTo.java b/src/main/java/de/juplo/demo/MessageTo.java
new file mode 100644 (file)
index 0000000..c024dc1
--- /dev/null
@@ -0,0 +1,21 @@
+package de.juplo.demo;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
+
+
+@Data
+@AllArgsConstructor(staticName = "of")
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(NON_EMPTY)
+public class MessageTo
+{
+  private String message;
+  private String from;
+  private String to;
+}
diff --git a/src/main/java/de/juplo/demo/RequestTypeInterceptor.java b/src/main/java/de/juplo/demo/RequestTypeInterceptor.java
new file mode 100644 (file)
index 0000000..fa1f428
--- /dev/null
@@ -0,0 +1,88 @@
+package de.juplo.demo;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.io.IOException;
+
+
+@Component
+@Slf4j
+public class RequestTypeInterceptor implements HandlerInterceptor
+{
+  public final static String REQUEST_TYPE = "X-Request-Type";
+
+  public enum RequestType { HUMAN, MACHINE };
+
+
+  @Override
+  public boolean preHandle(
+      HttpServletRequest request,
+      HttpServletResponse response,
+      Object handler) throws IOException
+  {
+    //  Only instances of HandlerMethod are intercepted for the type of the request
+    if (!(handler instanceof HandlerMethod))
+    {
+      log.trace("Ignoring handler of type {}", handler);
+      return true;
+    }
+
+    String requestTypeHeader = request.getHeader(REQUEST_TYPE);
+    if (requestTypeHeader == null)
+    {
+      sendBadRequest(response, "Ignoring request without header");
+      return false;
+    }
+
+    try
+    {
+      RequestType requestType = RequestType.valueOf(requestTypeHeader.toUpperCase());
+
+      if (requestType == RequestType.MACHINE)
+      {
+        log.debug("Accepting machine-request for {}", handler);
+        return true;
+      }
+
+      if (((HandlerMethod)handler).getMethod().getAnnotation(RequestableByHumans.class) != null)
+      {
+        log.info("Accepting human request for {}", handler);
+        return true;
+      }
+      else
+      {
+        sendForbidden(response, "Human requests are not allowed for " + handler);
+        return false;
+      }
+    }
+    catch (IllegalArgumentException e)
+    {
+      log.warn("Invalid RequestType: {}", e.toString());
+    }
+
+    sendBadRequest(response, requestTypeHeader);
+    return false;
+  }
+
+  void sendForbidden(
+      HttpServletResponse response,
+      String message) throws IOException
+  {
+    log.warn("Forbidden: {}", message);
+    response.sendError(HttpStatus.FORBIDDEN.value(), message);
+  }
+
+  void sendBadRequest(
+      HttpServletResponse response,
+      String message) throws IOException
+  {
+    log.warn("Bad request: {}", message);
+    response.sendError(HttpStatus.BAD_REQUEST.value(), message);
+  }
+}
diff --git a/src/main/java/de/juplo/demo/RequestableByHumans.java b/src/main/java/de/juplo/demo/RequestableByHumans.java
new file mode 100644 (file)
index 0000000..d5f33c4
--- /dev/null
@@ -0,0 +1,14 @@
+package de.juplo.demo;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequestableByHumans
+{
+}
index 576544f..0c07e93 100644 (file)
@@ -7,7 +7,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.web.servlet.MockMvc;
 
-import static de.juplo.demo.BackendVersionInterceptor.BACKEND_VERSION;
+import static de.juplo.demo.RequestTypeInterceptor.REQUEST_TYPE;
+import static de.juplo.demo.RequestTypeInterceptor.RequestType.HUMAN;
+import static de.juplo.demo.RequestTypeInterceptor.RequestType.MACHINE;
 import static org.springframework.http.HttpHeaders.ACCEPT;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -31,60 +33,95 @@ class DemoApplicationTests
 
 
        @Test
-       void getWelcomeResponseIsOkIfBackendHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
+       void getWelcomeResponseIsOkIfRequestTypeHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
        {
-               mvc.perform(get("/")).andExpect(status().isOk());
+               mvc
+                               .perform(get("/"))
+                               .andExpect(status().isOk());
        }
 
        @Test
-       void getWelcomeResponseIsOkIfBackendHeaderDoesMatch(@Autowired MockMvc mvc) throws Exception
+       void getWelcomeResponseIsOkIfRequestIsOfTypeMachine(@Autowired MockMvc mvc) throws Exception
        {
                mvc
                                .perform(get("/")
-                                               .header(BACKEND_VERSION, projectVersion))
+                                               .header(REQUEST_TYPE, MACHINE))
                                .andExpect(status().isOk());
        }
 
        @Test
-       void getWelcomeResponseIsGoneIfBackendHeaderDoesNotMatch(@Autowired MockMvc mvc) throws Exception
+       void getWelcomeResponseIsOkIfRequestIsOfTypeHuman(@Autowired MockMvc mvc) throws Exception
        {
                mvc
                                .perform(get("/")
-                                               .header(BACKEND_VERSION, "FOO"))
-                               .andExpect(status().isGone());
+                                               .header(REQUEST_TYPE, HUMAN))
+                               .andExpect(status().isOk());
        }
 
        @Test
-       void getGreetingIfBackendHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
+       void expectedResultsForGreetIfRequestIsOfTypeMachine(@Autowired MockMvc mvc) throws Exception
        {
                mvc
-                               .perform(post("/peter")
+                               .perform(post("/greet/peter")
                                                .header(ACCEPT, "application/json")
-                                               .header(BACKEND_VERSION, projectVersion)
+                                               .header(REQUEST_TYPE, MACHINE)
                                                .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
                                .andExpect(status().isOk())
-                               .andExpect(content().json("{\"greeting\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
+                               .andExpect(content().json("{\"message\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
        }
 
        @Test
-       void getGreetingIfBackendHeaderDoesMatch(@Autowired MockMvc mvc) throws Exception
+       void expectedResultsForGreetIfRequestIsOfTypeHuman(@Autowired MockMvc mvc) throws Exception
        {
                mvc
-                               .perform(post("/peter")
+                               .perform(post("/greet/peter")
                                                .header(ACCEPT, "application/json")
-                                               .header(BACKEND_VERSION, "FOO")
+                                               .header(REQUEST_TYPE, HUMAN)
                                                .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
-                               .andExpect(status().isGone());
+                               .andExpect(status().isOk())
+                               .andExpect(content().json("{\"message\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
        }
 
        @Test
-       void getGreetingIfBackendHeaderDoesNotMatch(@Autowired MockMvc mvc) throws Exception
+       void badRequestForGreetIfRequestTypeHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
        {
                mvc
-                               .perform(post("/peter")
+                               .perform(post("/greet/peter")
                                                .header(ACCEPT, "application/json")
                                                .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
+                               .andExpect(status().isBadRequest());
+       }
+
+       @Test
+       void expectedResultsForAcknowlegeIfRequestIsOfTypeMachine(@Autowired MockMvc mvc) throws Exception
+       {
+               mvc
+                               .perform(post("/acknowledge/peter")
+                                               .header(ACCEPT, "application/json")
+                                               .header(REQUEST_TYPE, MACHINE)
+                                               .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
                                .andExpect(status().isOk())
-                               .andExpect(content().json("{\"greeting\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
+                               .andExpect(content().json("{\"message\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
+       }
+
+       @Test
+       void isForbiddenForAcknowlegeIfRequestIsOfTypeHuman(@Autowired MockMvc mvc) throws Exception
+       {
+               mvc
+                               .perform(post("/acknowledge/peter")
+                                               .header(ACCEPT, "application/json")
+                                               .header(REQUEST_TYPE, HUMAN)
+                                               .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
+                               .andExpect(status().isForbidden());
+       }
+
+       @Test
+       void badRequestForAcknowlegeIfRequestTypeHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
+       {
+               mvc
+                               .perform(post("/acknowledge/peter")
+                                               .header(ACCEPT, "application/json")
+                                               .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
+                               .andExpect(status().isBadRequest());
        }
 }