Skip to content

Commit c84cd60

Browse files
AarchyAron Meszaros
authored and
Aron Meszaros
committed
[CALCITE-6135] BEARER authentication support
1 parent 01a7a9e commit c84cd60

20 files changed

+1362
-2
lines changed

bom/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ dependencies {
7878
apiv("org.ow2.asm:asm-tree", "asm")
7979
apiv("org.ow2.asm:asm-util", "asm")
8080
apiv("org.slf4j:slf4j-api", "slf4j")
81+
apiv("commons-io:commons-io")
8182
// The log4j2 binding should be a runtime dependency but given that
8283
// some modules shade this dependency we need to keep it as api
8384
apiv("org.apache.logging.log4j:log4j-slf4j-impl", "log4j2")

core/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
testImplementation("org.mockito:mockito-core")
4343
testImplementation("org.mockito:mockito-inline")
4444
testImplementation("org.hamcrest:hamcrest-core")
45+
testImplementation("commons-io:commons-io")
4546
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j-impl")
4647
}
4748

core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ public enum BuiltInConnectionProperty implements ConnectionProperty {
127127
* HTTP Connection Timeout in milliseconds.
128128
*/
129129
HTTP_CONNECTION_TIMEOUT("http_connection_timeout",
130-
Type.NUMBER, Timeout.ofMinutes(3).toMilliseconds(), false);
130+
Type.NUMBER, Timeout.ofMinutes(3).toMilliseconds(), false),
131+
132+
/** File containing bearer tokens. */
133+
TOKEN_FILE("tokenfile", Type.STRING, "", false);
131134

132135
private final String camelName;
133136
private final Type type;

core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public interface ConnectionConfig {
8181
long getLBConnectionFailoverSleepTime();
8282
/** @see BuiltInConnectionProperty#HTTP_CONNECTION_TIMEOUT **/
8383
long getHttpConnectionTimeout();
84+
/** @see BuiltInConnectionProperty#TOKEN_FILE */
85+
String tokenFile();
8486
}
8587

8688
// End ConnectionConfig.java

core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java

+4
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ public long getHttpConnectionTimeout() {
169169
return BuiltInConnectionProperty.HTTP_CONNECTION_TIMEOUT.wrap(properties).getLong();
170170
}
171171

172+
public String tokenFile() {
173+
return BuiltInConnectionProperty.TOKEN_FILE.wrap(properties).getString();
174+
}
175+
172176
/** Converts a {@link Properties} object containing (name, value)
173177
* pairs into a map whose keys are
174178
* {@link org.apache.calcite.avatica.InternalProperty} objects.

core/src/main/java/org/apache/calcite/avatica/remote/AuthenticationType.java

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public enum AuthenticationType {
2424
BASIC,
2525
DIGEST,
2626
SPNEGO,
27+
BEARER,
2728
CUSTOM;
2829
}
2930

core/src/main/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImpl.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
import java.net.URISyntaxException;
6161
import java.net.URL;
6262
import java.security.Principal;
63+
import java.util.Arrays;
64+
import java.util.Collections;
65+
import java.util.List;
6366
import java.util.Objects;
6467
import java.util.concurrent.TimeUnit;
6568

@@ -68,7 +71,7 @@
6871
* sent and received across the wire.
6972
*/
7073
public class AvaticaCommonsHttpClientImpl implements AvaticaHttpClient, HttpClientPoolConfigurable,
71-
UsernamePasswordAuthenticateable, GSSAuthenticateable {
74+
UsernamePasswordAuthenticateable, GSSAuthenticateable, BearerAuthenticateable {
7275
private static final Logger LOG = LoggerFactory.getLogger(AvaticaCommonsHttpClientImpl.class);
7376

7477
// SPNEGO specific settings
@@ -91,6 +94,10 @@ public class AvaticaCommonsHttpClientImpl implements AvaticaHttpClient, HttpClie
9194
protected CredentialsProvider credentialsProvider = null;
9295
protected Lookup<AuthSchemeFactory> authRegistry = null;
9396
protected Object userToken;
97+
private static final List<String> AVATICA_SCHEME_PRIORITY =
98+
Collections.unmodifiableList(Arrays.asList(StandardAuthScheme.BASIC,
99+
StandardAuthScheme.DIGEST, StandardAuthScheme.SPNEGO, StandardAuthScheme.NTLM,
100+
StandardAuthScheme.KERBEROS, "Bearer"));
94101

95102
public AvaticaCommonsHttpClientImpl(URL url) {
96103
this.uri = toURI(Objects.requireNonNull(url));
@@ -104,6 +111,7 @@ protected void initializeClient(PoolingHttpClientConnectionManager pool,
104111
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
105112
RequestConfig requestConfig = requestConfigBuilder
106113
.setConnectTimeout(config.getHttpConnectionTimeout(), TimeUnit.MILLISECONDS)
114+
.setTargetPreferredAuthSchemes(AVATICA_SCHEME_PRIORITY)
107115
.build();
108116
HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(pool)
109117
.setDefaultRequestConfig(requestConfig);
@@ -206,6 +214,17 @@ CloseableHttpResponse execute(HttpPost post, HttpClientContext context)
206214
}
207215
}
208216

217+
@Override public void setTokenProvider(String username, BearerTokenProvider tokenProvider) {
218+
this.credentialsProvider = new BasicCredentialsProvider();
219+
((BasicCredentialsProvider) this.credentialsProvider)
220+
.setCredentials(anyAuthScope, new BearerCredentials(username, tokenProvider));
221+
222+
this.authRegistry = RegistryBuilder.<AuthSchemeFactory>create()
223+
.register("Bearer",
224+
new BearerSchemeFactory())
225+
.build();
226+
}
227+
209228
/**
210229
* A credentials implementation which returns null.
211230
*/

core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java

+18
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,24 @@ public static AvaticaHttpClientFactoryImpl getInstance() {
130130
LOG.debug("{} is not capable of kerberos authentication.", authType);
131131
}
132132

133+
if (client instanceof BearerAuthenticateable) {
134+
if (AuthenticationType.BEARER == authType) {
135+
FileBearerTokenProvider tokenProvider = new FileBearerTokenProvider();
136+
try {
137+
tokenProvider.init(config);
138+
String username = config.avaticaUser();
139+
if (null == username) {
140+
username = System.getProperty("user.name");
141+
}
142+
((BearerAuthenticateable) client).setTokenProvider(username, tokenProvider);
143+
} catch (java.io.IOException e) {
144+
LOG.debug("Failed to initialize bearer authentication");
145+
}
146+
}
147+
} else {
148+
LOG.debug("{} is not capable of bearer authentication.", authType);
149+
}
150+
133151
if (null != kerberosUtil) {
134152
client = new DoAsAvaticaHttpClient(client, kerberosUtil);
135153
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.avatica.remote;
18+
19+
public interface BearerAuthenticateable {
20+
21+
void setTokenProvider(String username, BearerTokenProvider tokenProvider);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.avatica.remote;
18+
19+
import org.apache.hc.client5.http.auth.Credentials;
20+
import org.apache.hc.core5.annotation.Contract;
21+
import org.apache.hc.core5.annotation.ThreadingBehavior;
22+
import org.apache.hc.core5.util.Args;
23+
24+
import java.io.Serializable;
25+
import java.security.Principal;
26+
27+
@Contract(threading = ThreadingBehavior.IMMUTABLE)
28+
public class BearerCredentials implements Credentials, Serializable {
29+
30+
private final BearerTokenProvider tokenProvider;
31+
32+
private final String userName;
33+
34+
public BearerCredentials(final String userName, final BearerTokenProvider tokenProvider) {
35+
Args.notNull(userName, "userName");
36+
Args.notNull(tokenProvider, "tokenProvider");
37+
this.tokenProvider = tokenProvider;
38+
this.userName = userName;
39+
}
40+
41+
public String getToken() {
42+
return tokenProvider.obtain(userName);
43+
}
44+
45+
@Override
46+
public Principal getUserPrincipal() {
47+
return null;
48+
}
49+
50+
@Override
51+
public char[] getPassword() {
52+
return null;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.avatica.remote;
18+
19+
import org.apache.hc.client5.http.auth.AuthChallenge;
20+
import org.apache.hc.client5.http.auth.AuthScheme;
21+
import org.apache.hc.client5.http.auth.AuthScope;
22+
import org.apache.hc.client5.http.auth.AuthenticationException;
23+
import org.apache.hc.client5.http.auth.Credentials;
24+
import org.apache.hc.client5.http.auth.CredentialsProvider;
25+
import org.apache.hc.client5.http.auth.MalformedChallengeException;
26+
import org.apache.hc.core5.http.HttpHost;
27+
import org.apache.hc.core5.http.HttpRequest;
28+
import org.apache.hc.core5.http.NameValuePair;
29+
import org.apache.hc.core5.http.protocol.HttpContext;
30+
import org.apache.hc.core5.util.Args;
31+
import org.apache.hc.core5.util.Asserts;
32+
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
35+
36+
import java.io.Serializable;
37+
import java.security.Principal;
38+
import java.util.HashMap;
39+
import java.util.List;
40+
import java.util.Locale;
41+
import java.util.Map;
42+
43+
public class BearerScheme implements AuthScheme, Serializable {
44+
private static final Logger LOG = LoggerFactory.getLogger(BearerScheme.class);
45+
private String token;
46+
47+
private final Map<String, String> paramMap;
48+
private boolean complete;
49+
50+
public BearerScheme() {
51+
super();
52+
this.paramMap = new HashMap<>();
53+
this.complete = false;
54+
}
55+
56+
@Override
57+
public String getName() {
58+
return "Bearer";
59+
}
60+
61+
@Override
62+
public boolean isConnectionBased() {
63+
return false;
64+
}
65+
66+
@Override
67+
public String getRealm() {
68+
return this.paramMap.get("realm");
69+
}
70+
71+
@Override
72+
public void processChallenge(
73+
final AuthChallenge authChallenge,
74+
final HttpContext context) throws MalformedChallengeException {
75+
this.paramMap.clear();
76+
final List<NameValuePair> params = authChallenge.getParams();
77+
if (params != null) {
78+
for (final NameValuePair param: params) {
79+
this.paramMap.put(param.getName().toLowerCase(Locale.ROOT), param.getValue());
80+
}
81+
if (LOG.isDebugEnabled()) {
82+
final String error = paramMap.get("error");
83+
if (error != null) {
84+
final StringBuilder buf = new StringBuilder();
85+
buf.append(error);
86+
final String desc = paramMap.get("error_description");
87+
final String uri = paramMap.get("error_uri");
88+
if (desc != null || uri != null) {
89+
buf.append(" (");
90+
buf.append(desc).append("; ").append(uri);
91+
buf.append(")");
92+
}
93+
LOG.debug(buf.toString());
94+
}
95+
}
96+
}
97+
this.complete = true;
98+
}
99+
100+
@Override
101+
public boolean isChallengeComplete() {
102+
return this.complete;
103+
}
104+
105+
@Override
106+
public boolean isResponseReady(
107+
final HttpHost host,
108+
final CredentialsProvider credentialsProvider,
109+
final HttpContext context) throws AuthenticationException {
110+
Args.notNull(host, "Auth host");
111+
Args.notNull(credentialsProvider, "CredentialsProvider");
112+
113+
final Credentials credentials = credentialsProvider.getCredentials(
114+
new AuthScope(host, null, getName()), context);
115+
116+
if (!(credentials instanceof BearerCredentials)) {
117+
return false;
118+
}
119+
120+
this.token = ((BearerCredentials) credentials).getToken();
121+
return null != this.token;
122+
}
123+
124+
@Override
125+
public Principal getPrincipal() {
126+
return null;
127+
}
128+
129+
@Override
130+
public String generateAuthResponse(
131+
final HttpHost host,
132+
final HttpRequest request,
133+
final HttpContext context) throws AuthenticationException {
134+
Asserts.notNull(this.token, "Bearer token");
135+
return "Bearer " + this.token;
136+
}
137+
138+
@Override
139+
public String toString() {
140+
return getName() + this.paramMap;
141+
}
142+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.avatica.remote;
18+
19+
import org.apache.hc.client5.http.auth.AuthScheme;
20+
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
21+
import org.apache.hc.core5.annotation.Contract;
22+
import org.apache.hc.core5.annotation.ThreadingBehavior;
23+
import org.apache.hc.core5.http.protocol.HttpContext;
24+
25+
@Contract(threading = ThreadingBehavior.STATELESS)
26+
public class BearerSchemeFactory implements AuthSchemeFactory {
27+
public static final BearerSchemeFactory INSTANCE = new BearerSchemeFactory();
28+
29+
@Override
30+
public AuthScheme create(final HttpContext context) {
31+
return new BearerScheme();
32+
}
33+
34+
}

0 commit comments

Comments
 (0)