diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-04-24 16:52:49 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-04-24 17:24:11 +0200 |
commit | fd6b82e60eaf6b85800e7615d2382263a655478f (patch) | |
tree | 997d39f413441eca842b170e1b7fbff12543581f | |
parent | 04002b8760c2061e03285be71b353de3f502fa93 (diff) |
Add base class for security filters rendering errors as json
3 files changed, 151 insertions, 0 deletions
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java new file mode 100644 index 00000000000..e2440bc4c5f --- /dev/null +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBase.java @@ -0,0 +1,86 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.filter.security.base; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.handler.FastContentWriter; +import com.yahoo.jdisc.handler.ResponseDispatch; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.filter.DiscFilterRequest; +import com.yahoo.jdisc.http.filter.SecurityRequestFilter; + +import java.io.UncheckedIOException; +import java.util.Optional; + +/** + * A base class for {@link SecurityRequestFilter} implementations that renders an error response as JSON. + * + * @author bjorncs + */ +public abstract class JsonSecurityRequestFilterBase implements SecurityRequestFilter { + + private static final ObjectMapper mapper = new ObjectMapper(); + + @Override + public final void filter(DiscFilterRequest request, ResponseHandler handler) { + filter(request) + .ifPresent(errorResponse -> writeResponse(errorResponse, handler)); + } + + protected abstract Optional<ErrorResponse> filter(DiscFilterRequest request); + + private void writeResponse(ErrorResponse error, ResponseHandler responseHandler) { + ObjectNode errorMessage = mapper.createObjectNode(); + errorMessage.put("code", error.errorCode); + errorMessage.put("message", error.message); + error.response.headers().put("Content-Type", "application/json"); // Note: Overwrites header if already exists + try (FastContentWriter writer = ResponseDispatch.newInstance(error.response).connectFastWriter(responseHandler)) { + writer.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errorMessage)); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + + /** + * An error response that contains a {@link Response}, error code and message. + * The error code and message will be rendered as JSON fields with name 'code' and 'message' respectively. + */ + protected static class ErrorResponse { + private final Response response; + private final int errorCode; + private final String message; + + public ErrorResponse(Response response, int errorCode, String message) { + this.response = response; + this.errorCode = errorCode; + this.message = message; + } + + public ErrorResponse(Response response, String message) { + this(response, response.getStatus(), message); + } + + public ErrorResponse(int httpStatusCode, int errorCode, String message) { + this(new Response(httpStatusCode), errorCode, message); + } + + public ErrorResponse(int httpStatusCode, String message) { + this(new Response(httpStatusCode), message); + } + + public Response getResponse() { + return response; + } + + public int getErrorCode() { + return errorCode; + } + + public String getMessage() { + return message; + } + + } +} diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/package-info.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/package-info.java new file mode 100644 index 00000000000..38f7b126443 --- /dev/null +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/base/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.jdisc.http.filter.security.base; + +import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBaseTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBaseTest.java new file mode 100644 index 00000000000..9b0a875a73d --- /dev/null +++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/base/JsonSecurityRequestFilterBaseTest.java @@ -0,0 +1,57 @@ +package com.yahoo.jdisc.http.filter.security.base; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.container.jdisc.RequestHandlerTestDriver; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.http.filter.DiscFilterRequest; +import org.junit.Test; + +import java.io.IOException; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +/** + * @author bjorncs + */ +public class JsonSecurityRequestFilterBaseTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + public void filter_renders_errors_as_json() throws IOException { + int statusCode = 403; + String message = "Forbidden"; + DiscFilterRequest request = mock(DiscFilterRequest.class); + SimpleSecurityRequestFilter filter = + new SimpleSecurityRequestFilter(new JsonSecurityRequestFilterBase.ErrorResponse(statusCode, message)); + RequestHandlerTestDriver.MockResponseHandler responseHandler = new RequestHandlerTestDriver.MockResponseHandler(); + filter.filter(request, responseHandler); + + Response response = responseHandler.getResponse(); + assertThat(response, notNullValue()); + assertThat(response.getStatus(), equalTo(statusCode)); + + JsonNode jsonNode = mapper.readTree(responseHandler.readAll()); + assertThat(jsonNode.get("message").asText(), equalTo(message)); + assertThat(jsonNode.get("code").asInt(), equalTo(statusCode)); + } + + private static class SimpleSecurityRequestFilter extends JsonSecurityRequestFilterBase { + private final ErrorResponse errorResponse; + + SimpleSecurityRequestFilter(ErrorResponse errorResponse) { + this.errorResponse = errorResponse; + } + + @Override + protected Optional<ErrorResponse> filter(DiscFilterRequest request) { + return Optional.ofNullable(this.errorResponse); + } + } + +}
\ No newline at end of file |