diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ff504e..ba40484c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Fixed +* Replace non-ascii characters in headers to be in line with the service. +### Security +* No redirects by default + ## [4.0.4] - 2023-02-20 ### Added - Add new trident endpoint support diff --git a/data/src/main/java/com/microsoft/azure/kusto/data/ClientImpl.java b/data/src/main/java/com/microsoft/azure/kusto/data/ClientImpl.java index 0a716a81..612c7761 100644 --- a/data/src/main/java/com/microsoft/azure/kusto/data/ClientImpl.java +++ b/data/src/main/java/com/microsoft/azure/kusto/data/ClientImpl.java @@ -347,6 +347,8 @@ class ClientImpl implements Client, StreamingClient { activityId, activityId, activityId, clientRequestId); headers.put("x-ms-activitycontext", activityContext); + // replace non-ascii characters in header values with '?' + headers.replaceAll((_i, v) -> v == null ? null : v.replaceAll("[^\\x00-\\x7F]", "?")); return headers; } diff --git a/data/src/main/java/com/microsoft/azure/kusto/data/HttpClientFactory.java b/data/src/main/java/com/microsoft/azure/kusto/data/HttpClientFactory.java index f0047d6f..9db119af 100644 --- a/data/src/main/java/com/microsoft/azure/kusto/data/HttpClientFactory.java +++ b/data/src/main/java/com/microsoft/azure/kusto/data/HttpClientFactory.java @@ -37,7 +37,8 @@ public class HttpClientFactory { .setMaxConnTotal(properties.maxConnectionTotal()) .setMaxConnPerRoute(properties.maxConnectionRoute()) .evictExpiredConnections() - .evictIdleConnections(properties.maxIdleTime(), TimeUnit.SECONDS); + .evictIdleConnections(properties.maxIdleTime(), TimeUnit.SECONDS) + .disableRedirectHandling(); if (properties.isKeepAlive()) { final ConnectionKeepAliveStrategy keepAliveStrategy = new CustomConnectionKeepAliveStrategy(properties.maxKeepAliveTime()); diff --git a/data/src/main/java/com/microsoft/azure/kusto/data/UriUtils.java b/data/src/main/java/com/microsoft/azure/kusto/data/UriUtils.java index 71c4a400..b1f9baeb 100644 --- a/data/src/main/java/com/microsoft/azure/kusto/data/UriUtils.java +++ b/data/src/main/java/com/microsoft/azure/kusto/data/UriUtils.java @@ -26,6 +26,11 @@ public class UriUtils { return setPathForUri(uri, path, false); } + public static String appendPathToUri(String uri, String path) throws URISyntaxException { + String existing = new URIBuilder(uri).getPath(); + return setPathForUri(uri, StringUtils.appendIfMissing(existing == null ? "" : existing, "/") + path); + } + public static boolean isLocalAddress(String host) { if (host.equals("localhost") || host.equals("127.0.0.1") diff --git a/data/src/main/java/com/microsoft/azure/kusto/data/auth/CloudInfo.java b/data/src/main/java/com/microsoft/azure/kusto/data/auth/CloudInfo.java index 3aee4541..8d02d293 100644 --- a/data/src/main/java/com/microsoft/azure/kusto/data/auth/CloudInfo.java +++ b/data/src/main/java/com/microsoft/azure/kusto/data/auth/CloudInfo.java @@ -3,16 +3,16 @@ package com.microsoft.azure.kusto.data.auth; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.azure.kusto.data.HttpClientFactory; import com.microsoft.azure.kusto.data.UriUtils; - import com.microsoft.azure.kusto.data.Utils; import com.microsoft.azure.kusto.data.exceptions.DataServiceException; + import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.jetbrains.annotations.Nullable; @@ -90,9 +90,9 @@ public class CloudInfo { CloudInfo result; try { - HttpClient localHttpClient = givenHttpClient == null ? HttpClients.createSystem() : givenHttpClient; + HttpClient localHttpClient = givenHttpClient == null ? HttpClientFactory.create(null) : givenHttpClient; try { - HttpGet request = new HttpGet(UriUtils.setPathForUri(clusterUrl, METADATA_ENDPOINT)); + HttpGet request = new HttpGet(UriUtils.appendPathToUri(clusterUrl, METADATA_ENDPOINT)); request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip,deflate"); request.addHeader(HttpHeaders.ACCEPT, "application/json"); HttpResponse response = localHttpClient.execute(request); diff --git a/ingest/src/test/java/com/microsoft/azure/kusto/ingest/E2ETest.java b/ingest/src/test/java/com/microsoft/azure/kusto/ingest/E2ETest.java index 393bed14..a6c42084 100644 --- a/ingest/src/test/java/com/microsoft/azure/kusto/ingest/E2ETest.java +++ b/ingest/src/test/java/com/microsoft/azure/kusto/ingest/E2ETest.java @@ -3,12 +3,21 @@ package com.microsoft.azure.kusto.ingest; -import com.microsoft.azure.kusto.data.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.microsoft.azure.kusto.data.Client; +import com.microsoft.azure.kusto.data.ClientFactory; +import com.microsoft.azure.kusto.data.ClientRequestProperties; +import com.microsoft.azure.kusto.data.HttpClientProperties; +import com.microsoft.azure.kusto.data.KustoOperationResult; +import com.microsoft.azure.kusto.data.KustoResultSetTable; +import com.microsoft.azure.kusto.data.StreamingClient; +import com.microsoft.azure.kusto.data.Utils; import com.microsoft.azure.kusto.data.auth.CloudInfo; import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder; +import com.microsoft.azure.kusto.data.auth.endpoints.KustoTrustedEndpoints; +import com.microsoft.azure.kusto.data.auth.endpoints.MatchRule; import com.microsoft.azure.kusto.data.exceptions.DataClientException; import com.microsoft.azure.kusto.data.exceptions.DataServiceException; import com.microsoft.azure.kusto.data.format.CslDateTimeFormat; @@ -24,6 +33,7 @@ import com.microsoft.azure.kusto.ingest.source.StreamSourceInfo; import com.microsoft.azure.kusto.ingest.utils.ContainerWithSas; import com.microsoft.azure.kusto.ingest.utils.SecurityUtils; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.StopWatch; import org.apache.http.impl.client.CloseableHttpClient; @@ -119,6 +129,7 @@ class E2ETest { ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCredentials(System.getenv("ENGINE_CONNECTION_STRING"), appId, appKey, tenantId); + engineCsb.setUserNameForTracing("Java_E2ETest_ΓΈ"); try { streamingIngestClient = IngestClientFactory.createStreamingIngestClient(engineCsb); queryClient = ClientFactory.createClient(engineCsb); @@ -550,10 +561,58 @@ class E2ETest { Mockito.verify(httpClientSpy, atLeast(2)).execute(any()); } + @Test + void testNoRedirectsCloudFail() { + KustoTrustedEndpoints.addTrustedHosts(List.of(new MatchRule("statusreturner.azurewebsites.net", false)), false); + List redirectCodes = Arrays.asList(301, 302, 307, 308); + redirectCodes.parallelStream().map(code -> { + try (Client client = ClientFactory.createClient( + ConnectionStringBuilder.createWithAadAccessTokenAuthentication("https://statusreturner.azurewebsites.net/nocloud/" + code, "token"))) { + try { + client.execute("db", "table"); + Assertions.fail("Expected exception"); + } catch (DataServiceException e) { + Assertions.assertTrue(e.getMessage().contains("" + code)); + Assertions.assertTrue(e.getMessage().contains("metadata")); + } + } catch (Exception e) { + return e; + } + return null; + }).forEach(e -> { + if (e != null) { + Assertions.fail(e); + } + }); + } + + @Test + void testNoRedirectsClientFail() { + KustoTrustedEndpoints.addTrustedHosts(List.of(new MatchRule("statusreturner.azurewebsites.net", false)), false); + List redirectCodes = Arrays.asList(301, 302, 307, 308); + redirectCodes.parallelStream().map(code -> { + try (Client client = ClientFactory.createClient( + ConnectionStringBuilder.createWithAadAccessTokenAuthentication("https://statusreturner.azurewebsites.net/" + code, "token"))) { + try { + client.execute("db", "table"); + Assertions.fail("Expected exception"); + } catch (DataServiceException e) { + Assertions.assertTrue(e.getMessage().contains("" + code)); + Assertions.assertFalse(e.getMessage().contains("metadata")); + } + } catch (Exception e) { + return e; + } + return null; + }).forEach(e -> { + if (e != null) { + Assertions.fail(e); + } + }); + } + @Test void testStreamingIngestFromBlob() throws IngestionClientException, IngestionServiceException, IOException { - ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCredentials(System.getenv("ENGINE_CONNECTION_STRING"), appId, - appKey, tenantId); ResourceManager resourceManager = new ResourceManager(dmCslClient, null); ContainerWithSas container = resourceManager.getTempStorage(); AzureStorageClient azureStorageClient = new AzureStorageClient(); @@ -577,6 +636,5 @@ class E2ETest { assertRowCount(item.rows, false); } } - } }