Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-6135] BEARER authentication support #232

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Apache Calcite Avatica release 1.23.0
Apache Calcite Avatica release 1.24.0

# Overview
This is a source or binary distribution of Avatica, a framework for
Expand Down
1 change: 1 addition & 0 deletions bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ dependencies {
apiv("org.ow2.asm:asm-tree", "asm")
apiv("org.ow2.asm:asm-util", "asm")
apiv("org.slf4j:slf4j-api", "slf4j")
apiv("commons-io:commons-io")
// The log4j2 binding should be a runtime dependency but given that
// some modules shade this dependency we need to keep it as api
apiv("org.apache.logging.log4j:log4j-slf4j-impl", "log4j2")
Expand Down
1 change: 1 addition & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-inline")
testImplementation("org.hamcrest:hamcrest-core")
testImplementation("commons-io:commons-io")
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j-impl")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,13 @@ public enum BuiltInConnectionProperty implements ConnectionProperty {
* HTTP Connection Timeout in milliseconds.
*/
HTTP_CONNECTION_TIMEOUT("http_connection_timeout",
Type.NUMBER, Timeout.ofMinutes(3).toMilliseconds(), false);
Type.NUMBER, Timeout.ofMinutes(3).toMilliseconds(), false),

/** Bearer token to use to perform Bearer authentication. */
BEARER_TOKEN("bearertoken", Type.STRING, null, false),

/** Classname of the BearerTokenProvider. */
TOKEN_PROVIDER_CLASS("bearer_token_provider_class", Type.STRING, null, false);

private final String camelName;
private final Type type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ public interface ConnectionConfig {
long getLBConnectionFailoverSleepTime();
/** @see BuiltInConnectionProperty#HTTP_CONNECTION_TIMEOUT **/
long getHttpConnectionTimeout();
/** @see BuiltInConnectionProperty#BEARER_TOKEN */
String bearerToken();
/** @see BuiltInConnectionProperty#TOKEN_PROVIDER_CLASS */
String bearerTokenProviderClass();

ConnectionPropertyValue customPropertyValue(ConnectionProperty property);
}

// End ConnectionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ public long getLBConnectionFailoverSleepTime() {
public long getHttpConnectionTimeout() {
return BuiltInConnectionProperty.HTTP_CONNECTION_TIMEOUT.wrap(properties).getLong();
}
public String bearerToken() {
return BuiltInConnectionProperty.BEARER_TOKEN.wrap(properties).getString();
}

public String bearerTokenProviderClass() {
return BuiltInConnectionProperty.TOKEN_PROVIDER_CLASS.wrap(properties).getString();
}

public ConnectionPropertyValue customPropertyValue(ConnectionProperty property) {
return property.wrap(properties);
}

/** Converts a {@link Properties} object containing (name, value)
* pairs into a map whose keys are
Expand Down Expand Up @@ -198,7 +209,7 @@ public static Map<ConnectionProperty, String> parse(Properties properties,
}

/** The combination of a property definition and a map of property values. */
public static class PropEnv {
public static class PropEnv implements ConnectionPropertyValue {
final Map<? extends ConnectionProperty, String> map;
private final ConnectionProperty property;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public interface ConnectionProperty {

/** Wraps this property with a properties object from which its value can be
* obtained when needed. */
ConnectionConfigImpl.PropEnv wrap(Properties properties);
ConnectionPropertyValue wrap(Properties properties);

/** Whether the property is mandatory. */
boolean required();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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.calcite.avatica;

public interface ConnectionPropertyValue {
/**
* Returns the string value of this property, or null if not specified and
* no default.
*/
String getString();

/**
* Returns the string value of this property, or null if not specified and
* no default.
*/
String getString(String defaultValue);

/**
* Returns the int value of this property. Throws if not set and no
* default.
*/
int getInt();

/**
* Returns the int value of this property. Throws if not set and no
* default.
*/
int getInt(Number defaultValue);

/**
* Returns the long value of this property. Throws if not set and no
* default.
*/
long getLong();

/**
* Returns the long value of this property. Throws if not set and no
* default.
*/
long getLong(Number defaultValue);

/**
* Returns the double value of this property. Throws if not set and no
* default.
*/
double getDouble();

/**
* Returns the double value of this property. Throws if not set and no
* default.
*/
double getDouble(Number defaultValue);

/**
* Returns the boolean value of this property. Throws if not set and no
* default.
*/
boolean getBoolean();

/**
* Returns the boolean value of this property. Throws if not set and no
* default.
*/
boolean getBoolean(boolean defaultValue);

/**
* Returns the enum value of this property. Throws if not set and no
* default.
*/
<E extends Enum<E>> E getEnum(Class<E> enumClass);

/**
* Returns the enum value of this property. Throws if not set and no
* default.
*/
<E extends Enum<E>> E getEnum(Class<E> enumClass, E defaultValue);

/**
* Returns an instance of a plugin.
*
* <p>Throws if not set and no default.
* Also throws if the class does not implement the required interface,
* or if it does not have a public default constructor or an public static
* field called {@code #INSTANCE}.
*/
<T> T getPlugin(Class<T> pluginClass, T defaultInstance);

/**
* Returns an instance of a plugin, using a given class name if none is
* set.
*
* <p>Throws if not set and no default.
* Also throws if the class does not implement the required interface,
* or if it does not have a public default constructor or an public static
* field called {@code #INSTANCE}.
*/
<T> T getPlugin(Class<T> pluginClass, String defaultClassName,
T defaultInstance);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum AuthenticationType {
BASIC,
DIGEST,
SPNEGO,
BEARER,
CUSTOM;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

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

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

public AvaticaCommonsHttpClientImpl(URL url) {
this.uri = toURI(Objects.requireNonNull(url));
Expand All @@ -104,6 +111,7 @@ protected void initializeClient(PoolingHttpClientConnectionManager pool,
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
RequestConfig requestConfig = requestConfigBuilder
.setConnectTimeout(config.getHttpConnectionTimeout(), TimeUnit.MILLISECONDS)
.setTargetPreferredAuthSchemes(AVATICA_SCHEME_PRIORITY)
.build();
HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(pool)
.setDefaultRequestConfig(requestConfig);
Expand Down Expand Up @@ -206,6 +214,17 @@ CloseableHttpResponse execute(HttpPost post, HttpClientContext context)
}
}

@Override public void setTokenProvider(String username, BearerTokenProvider tokenProvider) {
this.credentialsProvider = new BasicCredentialsProvider();
((BasicCredentialsProvider) this.credentialsProvider)
.setCredentials(anyAuthScope, new BearerCredentials(username, tokenProvider));

this.authRegistry = RegistryBuilder.<AuthSchemeFactory>create()
.register("Bearer",
new BearerSchemeFactory())
.build();
}

/**
* A credentials implementation which returns null.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ public static AvaticaHttpClientFactoryImpl getInstance() {
LOG.debug("{} is not capable of kerberos authentication.", authType);
}

if (client instanceof BearerAuthenticateable) {
if (AuthenticationType.BEARER == authType) {
try {
BearerTokenProvider tokenProvider =
BearerTokenProviderFactory.getBearerTokenProvider(config);
String username = config.avaticaUser();
if (null == username) {
username = System.getProperty("user.name");
}
((BearerAuthenticateable) client).setTokenProvider(username, tokenProvider);
} catch (java.io.IOException e) {
LOG.debug("Failed to initialize bearer authentication");
}
}
} else {
LOG.debug("{} is not capable of bearer authentication.", authType);
}

if (null != kerberosUtil) {
client = new DoAsAvaticaHttpClient(client, kerberosUtil);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.calcite.avatica.remote;

public interface BearerAuthenticateable {

void setTokenProvider(String username, BearerTokenProvider tokenProvider);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.calcite.avatica.remote;

import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.util.Args;

import java.io.Serializable;
import java.security.Principal;

@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class BearerCredentials implements Credentials, Serializable {

private final BearerTokenProvider tokenProvider;

private final String userName;

public BearerCredentials(final String userName, final BearerTokenProvider tokenProvider) {
Args.notNull(userName, "userName");
Args.notNull(tokenProvider, "tokenProvider");
this.tokenProvider = tokenProvider;
this.userName = userName;
}

public String getToken() {
return tokenProvider.obtain(userName);
}

@Override
public Principal getUserPrincipal() {
return null;
}

@Override
public char[] getPassword() {
return null;
}
}
Loading