diff --git a/java/core/src/main/java/co/worklytics/psoxy/gateway/impl/ApiDataRequestHandler.java b/java/core/src/main/java/co/worklytics/psoxy/gateway/impl/ApiDataRequestHandler.java index f2361fac61..14aad1fe48 100644 --- a/java/core/src/main/java/co/worklytics/psoxy/gateway/impl/ApiDataRequestHandler.java +++ b/java/core/src/main/java/co/worklytics/psoxy/gateway/impl/ApiDataRequestHandler.java @@ -1,61 +1,5 @@ package co.worklytics.psoxy.gateway.impl; -import java.io.IOException; -import java.net.ConnectException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.classic.methods.HttpHead; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpHeaders; -import org.apache.hc.core5.http.NameValuePair; -import org.apache.hc.core5.http.message.BasicNameValuePair; -import org.apache.hc.core5.net.WWWFormCodec; -import org.apache.http.HttpStatus; -import org.apache.http.client.utils.URIBuilder; -import com.avaulta.gateway.pseudonyms.PseudonymImplementation; -import com.avaulta.gateway.pseudonyms.impl.UrlSafeTokenPseudonymEncoder; -import com.avaulta.gateway.tokens.ReversibleTokenizationStrategy; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.api.client.http.ByteArrayContent; -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpContent; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestFactory; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpTransport; -import com.google.auth.Credentials; -import com.google.auth.http.HttpCredentialsAdapter; -import com.google.auth.http.HttpTransportFactory; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; import co.worklytics.psoxy.ControlHeader; import co.worklytics.psoxy.ErrorCauses; import co.worklytics.psoxy.ProcessedDataMetadataFields; @@ -69,6 +13,7 @@ import co.worklytics.psoxy.gateway.HttpEventRequest; import co.worklytics.psoxy.gateway.HttpEventResponse; import co.worklytics.psoxy.gateway.ProcessedContent; +import co.worklytics.psoxy.gateway.ProxyConstants; import co.worklytics.psoxy.gateway.SecretStore; import co.worklytics.psoxy.gateway.SourceAuthStrategy; import co.worklytics.psoxy.gateway.impl.output.OutputUtils; @@ -81,6 +26,28 @@ import co.worklytics.psoxy.utils.ComposedHttpRequestInitializer; import co.worklytics.psoxy.utils.GzipedContentHttpRequestInitializer; import co.worklytics.psoxy.utils.URLUtils; +import com.avaulta.gateway.pseudonyms.PseudonymImplementation; +import com.avaulta.gateway.pseudonyms.impl.UrlSafeTokenPseudonymEncoder; +import com.avaulta.gateway.tokens.ReversibleTokenizationStrategy; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.auth.Credentials; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.http.HttpTransportFactory; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import dagger.Lazy; import lombok.AllArgsConstructor; import lombok.Builder; @@ -90,7 +57,32 @@ import lombok.SneakyThrows; import lombok.Value; import lombok.extern.java.Log; -import co.worklytics.psoxy.gateway.ProxyConstants; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.net.WWWFormCodec; +import org.apache.http.HttpStatus; +import org.apache.http.client.utils.URIBuilder; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import java.io.IOException; +import java.net.ConnectException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.regex.Pattern; +import java.util.stream.Collectors; @NoArgsConstructor(onConstructor_ = @Inject) @Log @@ -241,6 +233,15 @@ public HttpEventResponse handle(HttpEventRequest requestToProxy, // our canonicalization code for this to go wrong log.log(Level.WARNING, "Error parsing / building request URL", e); + // InvalidTokenException extends RuntimeException + if (e instanceof ReversibleTokenizationStrategy.InvalidTokenException ite) { + return HttpEventResponse.builder() + .statusCode(HttpStatus.SC_UNPROCESSABLE_ENTITY) + .header(ProcessedDataMetadataFields.ERROR.getHttpHeader(), + ite.getErrorCode().name()) + .build(); + } + return HttpEventResponse.builder() .statusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR) .header(ProcessedDataMetadataFields.ERROR.getHttpHeader(), @@ -375,7 +376,7 @@ public HttpEventResponse handle(HttpEventRequest requestToProxy, if (isSocketTimeoutException(e)) { return buildNetworkTimeoutErrorResponse(builder, e); } - + builder.statusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); builder.body("Failed to parse request; review logs"); builder.header(ProcessedDataMetadataFields.ERROR.getHttpHeader(), @@ -394,9 +395,10 @@ public HttpEventResponse handle(HttpEventRequest requestToProxy, log.log(Level.WARNING, e.getMessage(), e); return builder.build(); } catch (ReversibleTokenizationStrategy.InvalidTokenException e) { - builder.statusCode(HttpStatus.SC_CONFLICT); + builder.statusCode(HttpStatus.SC_UNPROCESSABLE_ENTITY); builder.header(ProcessedDataMetadataFields.ERROR.getHttpHeader(), - ErrorCauses.TOKENIZED_REQUEST_PARAMETER_INVALID.name()); + e.getErrorCode().name()); + log.log(Level.WARNING, e.getMessage(), e); return builder.build(); } @@ -1045,7 +1047,7 @@ private boolean isSocketTimeoutException(Throwable throwable) { */ private HttpEventResponse buildNetworkTimeoutErrorResponse( HttpEventResponse.HttpEventResponseBuilder builder, Throwable e) { - + builder.statusCode(HttpStatus.SC_BAD_GATEWAY); builder.body("Network timeout: unable to connect to target API. " + "This could indicate: " + @@ -1054,12 +1056,12 @@ private HttpEventResponse buildNetworkTimeoutErrorResponse( "If using VPC connector, verify: VPC connector is active, CIDR range is correct, firewall allows egress, Cloud NAT is configured."); builder.header(ProcessedDataMetadataFields.ERROR.getHttpHeader(), ErrorCauses.NETWORK_EGRESS_BLOCKED.name()); - + log.log(Level.SEVERE, "SocketTimeoutException: Network timeout connecting to target API", e); log.log(Level.SEVERE, "Possible causes: " + "1) Proxy network egress blocked - check VPC connector configuration, firewall rules, Cloud NAT; " + "2) Target API unreachable or slow - check API status, DNS resolution"); - + return builder.build(); } diff --git a/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/ReversibleTokenizationStrategy.java b/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/ReversibleTokenizationStrategy.java index 77ff73bd61..9a22d0254c 100644 --- a/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/ReversibleTokenizationStrategy.java +++ b/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/ReversibleTokenizationStrategy.java @@ -1,5 +1,7 @@ package com.avaulta.gateway.tokens; +import lombok.Getter; + import java.util.function.Function; /** @@ -40,12 +42,31 @@ default byte[] getReversibleToken(String originalDatum) { /** - * Indicates that the token could not be reversed, likely because invalid - * + * Indicates that the token could not be reversed, likely because it is invalid. */ class InvalidTokenException extends RuntimeException { - public InvalidTokenException(String message, Throwable cause) { - super(message, cause); + + @Getter + public enum ErrorCode { + ALGORITHM_PARAMETER_ERROR("Failed to decrypt token; some algorithm parameter, such as iv, is wrong"), + BAD_PADDING("Failed to decrypt token; token appears to be corrupted or invalid, due to mismatch between token's padding and the padding expected by the cipher mode"), + ILLEGAL_BLOCK_SIZE("Failed to decrypt token; token appears to be corrupted or invalid, as block size seems to differ from expected"), + DECRYPTION_FAILED("Failed to decrypt token; most likely because encryption key has been rotated"); + + private final String message; + + ErrorCode(String message) { + this.message = message; + } + + } + + @Getter + private final ErrorCode errorCode; + + public InvalidTokenException(ErrorCode errorCode, Throwable cause) { + super(errorCode.name() + ": " + errorCode.getMessage(), cause); + this.errorCode = errorCode; } } } diff --git a/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/impl/AESReversibleTokenizationStrategy.java b/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/impl/AESReversibleTokenizationStrategy.java index f2e3d87025..41df37974b 100644 --- a/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/impl/AESReversibleTokenizationStrategy.java +++ b/java/gateway-core/src/main/java/com/avaulta/gateway/tokens/impl/AESReversibleTokenizationStrategy.java @@ -1,12 +1,14 @@ package com.avaulta.gateway.tokens.impl; -import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.KeySpec; -import java.util.Arrays; -import java.util.function.Function; +import com.avaulta.gateway.tokens.DeterministicTokenizationStrategy; +import com.avaulta.gateway.tokens.ReversibleTokenizationStrategy; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.Value; + import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; @@ -16,14 +18,13 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; -import com.avaulta.gateway.tokens.DeterministicTokenizationStrategy; -import com.avaulta.gateway.tokens.ReversibleTokenizationStrategy; -import lombok.Builder; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.Value; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.KeySpec; +import java.util.Arrays; +import java.util.function.Function; @Builder @RequiredArgsConstructor @@ -130,13 +131,13 @@ public String getOriginalDatum(@NonNull byte[] reversibleToken) throws Reversibl } catch (InvalidKeyException e) { throw new RuntimeException("Invalid encryption key configuration", e); } catch (InvalidAlgorithmParameterException e) { - throw new InvalidTokenException("Failed to decrypt token; some algorithm parameter, such as iv, is wrong", e); + throw new InvalidTokenException(InvalidTokenException.ErrorCode.ALGORITHM_PARAMETER_ERROR, e); } catch (BadPaddingException e) { - throw new InvalidTokenException("Failed to decrypt token; token appears to be corrupted or invalid, due to mismatch between token's padding and the padding expected by the cipher mode", e); + throw new InvalidTokenException(InvalidTokenException.ErrorCode.BAD_PADDING, e); } catch (IllegalBlockSizeException e) { - throw new InvalidTokenException("Failed to decrypt token; token appears to be corrupted or invalid, as block size seems to differ from expected", e); + throw new InvalidTokenException(InvalidTokenException.ErrorCode.ILLEGAL_BLOCK_SIZE, e); } catch (RuntimeException e) { - throw new InvalidTokenException("Failed to decrypt token; most likely because encryption key has been rotated", e); + throw new InvalidTokenException(InvalidTokenException.ErrorCode.DECRYPTION_FAILED, e); } }