This is an automated email from the ASF dual-hosted git repository.
cgivre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/drill.git
The following commit(s) were added to refs/heads/master by this push:
new 4e827e9 DRILL-8161: Add Global Credentials to HTTP Storage Plugin
(#2486)
4e827e9 is described below
commit 4e827e905b02bdbcacda2ff075610bf9449e8736
Author: Charles S. Givre <[email protected]>
AuthorDate: Wed Mar 9 17:43:08 2022 -0500
DRILL-8161: Add Global Credentials to HTTP Storage Plugin (#2486)
* DRILL-8161: Add Global Credentials to HTTP Storage Plugin
* Removed unused import
* Formatting from code review
* Updated README
---
contrib/storage-http/README.md | 16 +++++++
.../exec/store/http/HttpStoragePluginConfig.java | 35 ++++++++++++---
.../drill/exec/store/http/util/SimpleHttp.java | 33 +++++++++++++-
.../drill/exec/store/http/TestHttpPlugin.java | 17 +++++++-
.../drill/exec/store/http/TestOAuthProcess.java | 2 +-
.../exec/store/http/TestOAuthTokenUpdate.java | 2 +-
.../drill/exec/store/http/TestPagination.java | 10 ++++-
.../store/security/CredentialProviderUtils.java | 12 +++++-
.../UsernamePasswordWithProxyCredentials.java | 50 ++++++++++++++++++++++
.../security/oauth/OAuthTokenCredentials.java | 2 +
10 files changed, 164 insertions(+), 15 deletions(-)
diff --git a/contrib/storage-http/README.md b/contrib/storage-http/README.md
index 40a57bf..8154f0a 100644
--- a/contrib/storage-http/README.md
+++ b/contrib/storage-http/README.md
@@ -282,6 +282,22 @@ If the `authType` is set to `basic`, `username` and
`password` must be set in th
`password`: The password for basic authentication.
+##### Global Credentials
+If you have an HTTP plugin with multiple endpoints that all use the same
credentials, you can set the `authType` to `basic` and set global
+credentials in the storage plugin configuration.
+
+Simply add the following to the storage plugin configuration:
+```json
+ "credentialsProvider": {
+ "credentialsProviderType": "PlainCredentialsProvider",
+ "credentials": {
+ "username": "user1",
+ "password": "user1Pass"
+ }
+ }
+```
+Note that the `authType` still must be set to `basic` and that any endpoint
credentials will override the global credentials.
+
#### Limiting Results
Some APIs support a query parameter which is used to limit the number of
results returned by the API. In this case you can set the `limitQueryParam`
config variable to the query parameter name and Drill will automatically
include this in your query. For instance, if you have an API which supports a
limit query parameter called `maxRecords` and you set the abovementioned config
variable then execute the following query:
diff --git
a/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/HttpStoragePluginConfig.java
b/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/HttpStoragePluginConfig.java
index ba32c82..721db6a 100644
---
a/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/HttpStoragePluginConfig.java
+++
b/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/HttpStoragePluginConfig.java
@@ -28,8 +28,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.apache.drill.exec.store.security.CredentialProviderUtils;
import org.apache.drill.common.logical.security.CredentialsProvider;
+import
org.apache.drill.exec.store.security.UsernamePasswordWithProxyCredentials;
import org.apache.drill.exec.store.security.oauth.OAuthTokenCredentials;
-import org.apache.drill.exec.store.security.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,6 +60,8 @@ public class HttpStoragePluginConfig extends
AbstractSecuredStoragePluginConfig
public HttpStoragePluginConfig(@JsonProperty("cacheResults") Boolean
cacheResults,
@JsonProperty("connections") Map<String,
HttpApiConfig> connections,
@JsonProperty("timeout") Integer timeout,
+ @JsonProperty("username") String username,
+ @JsonProperty("password") String password,
@JsonProperty("proxyHost") String proxyHost,
@JsonProperty("proxyPort") Integer proxyPort,
@JsonProperty("proxyType") String proxyType,
@@ -72,6 +74,8 @@ public class HttpStoragePluginConfig extends
AbstractSecuredStoragePluginConfig
getClientID(new OAuthTokenCredentials(credentialsProvider)),
getClientSecret(new OAuthTokenCredentials(credentialsProvider)),
getTokenURL(new OAuthTokenCredentials(credentialsProvider)),
+ normalize(username),
+ normalize(password),
normalize(proxyUsername),
normalize(proxyPassword),
credentialsProvider),
@@ -139,7 +143,10 @@ public class HttpStoragePluginConfig extends
AbstractSecuredStoragePluginConfig
public HttpStoragePluginConfig copyForPlan(String connectionName) {
return new HttpStoragePluginConfig(
cacheResults, configFor(connectionName), timeout,
- proxyHost, proxyPort, proxyType, null, null, oAuthConfig,
credentialsProvider);
+ getUsernamePasswordCredentials().getUsername(),
+ getUsernamePasswordCredentials().getPassword(),
+ proxyHost, proxyPort, proxyType,
getUsernamePasswordCredentials().getProxyUsername(),
+ getUsernamePasswordCredentials().getProxyPassword(), oAuthConfig,
credentialsProvider);
}
private Map<String, HttpApiConfig> configFor(String connectionName) {
@@ -205,10 +212,26 @@ public class HttpStoragePluginConfig extends
AbstractSecuredStoragePluginConfig
return oAuthConfig;
}
+ @JsonProperty("username")
+ public String username() {
+ if (directCredentials) {
+ return getUsernamePasswordCredentials().getUsername();
+ }
+ return null;
+ }
+
+ @JsonProperty("password")
+ public String password() {
+ if (directCredentials) {
+ return getUsernamePasswordCredentials().getPassword();
+ }
+ return null;
+ }
+
@JsonProperty("proxyUsername")
public String proxyUsername() {
if (directCredentials) {
- return getUsernamePasswordCredentials().getUsername();
+ return getUsernamePasswordCredentials().getProxyUsername();
}
return null;
}
@@ -216,7 +239,7 @@ public class HttpStoragePluginConfig extends
AbstractSecuredStoragePluginConfig
@JsonProperty("proxyPassword")
public String proxyPassword() {
if (directCredentials) {
- return getUsernamePasswordCredentials().getPassword();
+ return getUsernamePasswordCredentials().getProxyPassword();
}
return null;
}
@@ -245,8 +268,8 @@ public class HttpStoragePluginConfig extends
AbstractSecuredStoragePluginConfig
}
@JsonIgnore
- public UsernamePasswordCredentials getUsernamePasswordCredentials() {
- return new UsernamePasswordCredentials(credentialsProvider);
+ public UsernamePasswordWithProxyCredentials getUsernamePasswordCredentials()
{
+ return new UsernamePasswordWithProxyCredentials(credentialsProvider);
}
@JsonIgnore
diff --git
a/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/util/SimpleHttp.java
b/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/util/SimpleHttp.java
index 80dac2d..971e039 100644
---
a/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/util/SimpleHttp.java
+++
b/contrib/storage-http/src/main/java/org/apache/drill/exec/store/http/util/SimpleHttp.java
@@ -146,9 +146,10 @@ public class SimpleHttp {
builder.authenticator(new AccessTokenAuthenticator(repository));
builder.addInterceptor(new AccessTokenInterceptor(repository));
} else if (apiConfig.authType().equalsIgnoreCase("basic")) {
- // If the API uses basic authentication add the authentication code.
+ // If the API uses basic authentication add the authentication code.
Use the global credentials unless there are credentials
+ // for the specific endpoint.
logger.debug("Adding Interceptor");
- UsernamePasswordCredentials credentials =
apiConfig.getUsernamePasswordCredentials();
+ UsernamePasswordCredentials credentials = getCredentials();
builder.addInterceptor(new
BasicAuthInterceptor(credentials.getUsername(), credentials.getPassword()));
}
@@ -362,6 +363,34 @@ public class SimpleHttp {
}
/**
+ * Logic to determine whether the API connection has global credentials or
credentials specific for the
+ * API endpoint.
+ * @param endpointConfig The API endpoint configuration
+ * @return True if the endpoint has credentials, false if not.
+ */
+ private boolean hasEndpointCredentials(HttpApiConfig endpointConfig) {
+ UsernamePasswordCredentials credentials =
endpointConfig.getUsernamePasswordCredentials();
+ if (StringUtils.isNotEmpty(credentials.getUsername()) &&
+ StringUtils.isNotEmpty(credentials.getPassword())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If the user has defined username/password for the specific API endpoint,
pass the API endpoint credentials.
+ * Otherwise, use the global connection credentials.
+ * @return A UsernamePasswordCredentials collection with the correct
username/password
+ */
+ private UsernamePasswordCredentials getCredentials() {
+ if (hasEndpointCredentials(apiConfig)) {
+ return apiConfig.getUsernamePasswordCredentials();
+ } else {
+ return scanDefn.tableSpec().config().getUsernamePasswordCredentials();
+ }
+ }
+
+ /**
* Gets the HTTP response code from the HTTP call. Note that this value
* is only available after the getInputStream() method has been called.
*
diff --git
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestHttpPlugin.java
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestHttpPlugin.java
index 38e1f96..1ddbb05 100644
---
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestHttpPlugin.java
+++
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestHttpPlugin.java
@@ -52,6 +52,7 @@ import java.util.concurrent.TimeUnit;
import static org.apache.drill.test.rowSet.RowSetUtilities.mapArray;
import static org.apache.drill.test.rowSet.RowSetUtilities.mapValue;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -128,7 +129,7 @@ public class TestHttpPlugin extends ClusterTest {
configs.put("pokemon", pokemonConfig);
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
- new HttpStoragePluginConfig(false, configs, 10, "", 80, "", "", "",
null, PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
+ new HttpStoragePluginConfig(false, configs, 10, null, null, "", 80,
"", "", "", null, PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("live", mockStorageConfigWithWorkspace);
}
@@ -240,6 +241,7 @@ public class TestHttpPlugin extends ClusterTest {
HttpApiConfig mockPostConfigWithoutPostBody = HttpApiConfig.builder()
.url("http://localhost:8091/")
.method("POST")
+ .authType("basic")
.headers(headers)
.build();
@@ -332,7 +334,10 @@ public class TestHttpPlugin extends ClusterTest {
configs.put("mockJsonAllText", mockTableWithJsonOptions);
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
- new HttpStoragePluginConfig(false, configs, 2, "", 80, "", "", "",
null, PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
+ new HttpStoragePluginConfig(false, configs, 2, "globaluser",
"globalpass", "",
+ 80, "", "", "", null, new PlainCredentialsProvider(ImmutableMap.of(
+ UsernamePasswordCredentials.USERNAME, "globaluser",
+ UsernamePasswordCredentials.PASSWORD, "globalpass")));
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("local", mockStorageConfigWithWorkspace);
}
@@ -785,6 +790,11 @@ public class TestHttpPlugin extends ClusterTest {
.build();
RowSetUtilities.verify(expected, results);
+
+ // Verify correct username/password from endpoint configuration
+ RecordedRequest recordedRequest = server.takeRequest();
+ assertNotNull(recordedRequest.getHeader("Authorization"));
+ assertEquals("Basic dXNlcjpwYXNz",
recordedRequest.getHeader("Authorization"));
}
}
@@ -961,6 +971,9 @@ public class TestHttpPlugin extends ClusterTest {
RecordedRequest recordedRequest = server.takeRequest();
assertEquals("POST", recordedRequest.getMethod());
+ // Verify correct username/password from global configuration
+ assertNotNull(recordedRequest.getHeader("Authorization"));
+ assertEquals("Basic Z2xvYmFsdXNlcjpnbG9iYWxwYXNz",
recordedRequest.getHeader("Authorization"));
}
}
diff --git
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthProcess.java
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthProcess.java
index 943a331..521dcb2 100644
---
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthProcess.java
+++
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthProcess.java
@@ -109,7 +109,7 @@ public class TestOAuthProcess extends ClusterTest {
// Add storage plugin for test OAuth
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
- new HttpStoragePluginConfig(false, configs, TIMEOUT, "", 80, "", "", "",
+ new HttpStoragePluginConfig(false, configs, TIMEOUT, null, null, "", 80,
"", "", "",
oAuthConfig, credentialsProvider);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("localOauth", mockStorageConfigWithWorkspace);
diff --git
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthTokenUpdate.java
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthTokenUpdate.java
index 4171af9..dbadcd1 100644
---
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthTokenUpdate.java
+++
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestOAuthTokenUpdate.java
@@ -89,7 +89,7 @@ public class TestOAuthTokenUpdate extends ClusterTest {
// Add storage plugin for test OAuth
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
- new HttpStoragePluginConfig(false, configs, TIMEOUT, "", 80, "", "", "",
+ new HttpStoragePluginConfig(false, configs, TIMEOUT,null, null, "", 80,
"", "", "",
oAuthConfig, credentialsProvider);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("localOauth", mockStorageConfigWithWorkspace);
diff --git
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestPagination.java
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestPagination.java
index d5ad618..45f6263 100644
---
a/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestPagination.java
+++
b/contrib/storage-http/src/test/java/org/apache/drill/exec/store/http/TestPagination.java
@@ -20,6 +20,7 @@ package org.apache.drill.exec.store.http;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
import org.apache.drill.common.logical.security.PlainCredentialsProvider;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.common.util.DrillFileUtils;
@@ -47,6 +48,7 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
public class TestPagination extends ClusterTest {
private static final int MOCK_SERVER_PORT = 8092;
@@ -113,7 +115,7 @@ public class TestPagination extends ClusterTest {
configs.put("github", githubConfig);
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
- new HttpStoragePluginConfig(false, configs, 10, "", 80, "", "", "",
null, PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
+ new HttpStoragePluginConfig(false, configs, 10, null, null, "", 80, "",
"", "", null, PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("live", mockStorageConfigWithWorkspace);
}
@@ -193,7 +195,7 @@ public class TestPagination extends ClusterTest {
configs.put("xml_paginator_url_params",
mockXmlConfigWithPaginatorAndUrlParams);
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
- new HttpStoragePluginConfig(false, configs, 2, "", 80, "", "", "", null,
PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
+ new HttpStoragePluginConfig(false, configs, 2, null, null, "", 80, "",
"", "", null, PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("local", mockStorageConfigWithWorkspace);
}
@@ -349,6 +351,10 @@ public class TestPagination extends ClusterTest {
b.release();
}
assertEquals(6, count);
+
+ // Verify that there are no random headers being inserted if
authorization is not defined.
+ RecordedRequest recordedRequest = server.takeRequest();
+ assertNull(recordedRequest.getHeader("Authorization"));
}
}
diff --git
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/CredentialProviderUtils.java
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/CredentialProviderUtils.java
index 4ad2463..297c193 100644
---
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/CredentialProviderUtils.java
+++
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/CredentialProviderUtils.java
@@ -54,8 +54,10 @@ public class CredentialProviderUtils {
* @param clientID The OAuth Client ID. This is provided by the application
during signup.
* @param clientSecret The OAUth Client Secret. This is provided by the
application during signup.
* @param tokenURI The URI from which you swap the auth code for access and
refresh tokens.
- * @param username Optional username for proxy or other services
+ * @param username Optional username for other services
* @param password Optional password for proxy or other services
+ * @param proxyUsername Optional username for a proxy server.
+ * @param proxyPassword Optional password for a proxy server.
* @param credentialsProvider The credential store which retains the
credentials.
* @return A credential provider with the access tokens
*/
@@ -65,6 +67,8 @@ public class CredentialProviderUtils {
String tokenURI,
String username,
String password,
+ String proxyUsername,
+ String proxyPassword,
CredentialsProvider credentialsProvider) {
if (credentialsProvider != null) {
@@ -86,6 +90,12 @@ public class CredentialProviderUtils {
if (tokenURI != null) {
mapBuilder.put(OAuthTokenCredentials.TOKEN_URI, tokenURI);
}
+ if (proxyUsername != null) {
+ mapBuilder.put(OAuthTokenCredentials.PROXY_USERNAME, proxyUsername);
+ }
+ if (proxyPassword != null) {
+ mapBuilder.put(OAuthTokenCredentials.PROXY_PASSWORD, proxyPassword);
+ }
return new PlainCredentialsProvider(mapBuilder.build());
}
diff --git
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
new file mode 100644
index 0000000..7ae56cc
--- /dev/null
+++
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/UsernamePasswordWithProxyCredentials.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.drill.exec.store.security;
+
+import org.apache.drill.common.logical.security.CredentialsProvider;
+import org.apache.drill.exec.store.security.oauth.OAuthTokenCredentials;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class UsernamePasswordWithProxyCredentials extends
UsernamePasswordCredentials {
+ private final String proxyUsername;
+ private final String proxyPassword;
+
+ public UsernamePasswordWithProxyCredentials(CredentialsProvider
credentialsProvider) {
+ super(credentialsProvider);
+ if (credentialsProvider == null || credentialsProvider.getCredentials() ==
null) {
+ this.proxyUsername = null;
+ this.proxyPassword = null;
+ } else {
+ Map<String, String> credentials = credentialsProvider.getCredentials()
== null ? new HashMap<>() : credentialsProvider.getCredentials();
+ this.proxyUsername =
credentials.get(OAuthTokenCredentials.PROXY_USERNAME);
+ this.proxyPassword =
credentials.get(OAuthTokenCredentials.PROXY_PASSWORD);
+ }
+ }
+
+ public String getProxyUsername() {
+ return proxyUsername;
+ }
+
+ public String getProxyPassword() {
+ return proxyPassword;
+ }
+}
diff --git
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
index 2e39ee3..516d872 100644
---
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
+++
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/security/oauth/OAuthTokenCredentials.java
@@ -32,6 +32,8 @@ public class OAuthTokenCredentials extends
UsernamePasswordCredentials {
public static final String ACCESS_TOKEN = "accessToken";
public static final String REFRESH_TOKEN = "refreshToken";
public static final String TOKEN_URI = "tokenURI";
+ public static final String PROXY_USERNAME = "proxyUsername";
+ public static final String PROXY_PASSWORD = "proxyPassword";
private final String clientID;
private final String clientSecret;