Skip to content

Commit

Permalink
Extend ContentCachingRequestWrapper
Browse files Browse the repository at this point in the history
Spring's request wrapper does not support reading the input stream
multiple times with `getInputStream()` or `getReader()`, hence we need a
custom wrapper to cache to body.
  • Loading branch information
zechmeister committed Nov 4, 2024
1 parent ba2e764 commit 3136362
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.bund.digitalservice.a2j.config;

import de.bund.digitalservice.a2j.service.subscriber.CallbackVerificationFilter;
import de.bund.digitalservice.a2j.security.CallbackVerificationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package de.bund.digitalservice.a2j.security;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class CachedBodyServletInputStream extends ServletInputStream {

private final InputStream cachedBodyInputStream;

public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}

@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}

@Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package de.bund.digitalservice.a2j.security;

import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.util.StreamUtils;

public class CachedBodyServletRequest extends HttpServletRequestWrapper {

private byte[] cachedBody;

public CachedBodyServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}

@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.bund.digitalservice.a2j.service.subscriber;
package de.bund.digitalservice.a2j.security;

import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.client.SenderClient;
Expand All @@ -14,7 +14,6 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;

@Component
public class CallbackVerificationFilter extends OncePerRequestFilter {
Expand All @@ -40,22 +39,19 @@ protected void doFilterInternal(
@NotNull FilterChain chain)
throws ServletException, IOException {

ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
String requestBody = wrappedRequest.getReader().lines().collect(Collectors.joining("\n"));
CachedBodyServletRequest wrappedRequest = new CachedBodyServletRequest(request);

String hmac = request.getHeader("callback-authentication");
String body = wrappedRequest.getReader().lines().collect(Collectors.joining("\n"));

ValidationResult result =
senderClient.validateCallback(
hmac,
Long.parseLong(request.getHeader("callback-timestamp")),
requestBody,
callbackSecret);
hmac, Long.parseLong(request.getHeader("callback-timestamp")), body, callbackSecret);

if (!result.isValid()) {
logger.info("Received invalid fit-connect callback");
logger.info("hmac: " + hmac);
logger.info("body: " + requestBody);
logger.info("body: " + body);
logger.info("Validation Error: " + result.getError().getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
Expand Down

0 comments on commit 3136362

Please sign in to comment.