Skip to content

Commit

Permalink
Improve extensibility of plain text auth provider
Browse files Browse the repository at this point in the history
  • Loading branch information
olim7t committed Jun 25, 2019
1 parent 3189a80 commit 7c37f32
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 64 deletions.
Expand Up @@ -15,19 +15,11 @@
*/
package com.datastax.oss.driver.internal.core.auth;

import com.datastax.oss.driver.api.core.auth.AuthProvider;
import com.datastax.oss.driver.api.core.auth.Authenticator;
import com.datastax.oss.driver.api.core.auth.SyncAuthenticator;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.shaded.guava.common.base.Charsets;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.nio.ByteBuffer;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A simple authentication provider that supports SASL authentication using the PLAIN mechanism for
Expand All @@ -49,70 +41,20 @@
* See {@code reference.conf} (in the manual or core driver JAR) for more details.
*/
@ThreadSafe
public class PlainTextAuthProvider implements AuthProvider {
public class PlainTextAuthProvider extends PlainTextAuthProviderBase {

private static final Logger LOG = LoggerFactory.getLogger(PlainTextAuthProvider.class);

private final String logPrefix;
private final DriverExecutionProfile config;

/** Builds a new instance. */
public PlainTextAuthProvider(DriverContext context) {
this.logPrefix = context.getSessionName();
super(context.getSessionName());
this.config = context.getConfig().getDefaultProfile();
}

@NonNull
@Override
public Authenticator newAuthenticator(
@NonNull EndPoint endPoint, @NonNull String serverAuthenticator) {
String username = config.getString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME);
String password = config.getString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD);
return new PlainTextAuthenticator(username, password);
}

@Override
public void onMissingChallenge(@NonNull EndPoint endPoint) {
LOG.warn(
"[{}] {} did not send an authentication challenge; "
+ "This is suspicious because the driver expects authentication",
logPrefix,
endPoint);
}

@Override
public void close() throws Exception {
// nothing to do
}

private static class PlainTextAuthenticator implements SyncAuthenticator {

private final ByteBuffer initialToken;

PlainTextAuthenticator(String username, String password) {
byte[] usernameBytes = username.getBytes(Charsets.UTF_8);
byte[] passwordBytes = password.getBytes(Charsets.UTF_8);
this.initialToken = ByteBuffer.allocate(usernameBytes.length + passwordBytes.length + 2);
initialToken.put((byte) 0);
initialToken.put(usernameBytes);
initialToken.put((byte) 0);
initialToken.put(passwordBytes);
initialToken.flip();
}

@Override
public ByteBuffer initialResponseSync() {
return initialToken.duplicate();
}

@Override
public ByteBuffer evaluateChallengeSync(ByteBuffer token) {
return null;
}

@Override
public void onAuthenticationSuccessSync(ByteBuffer token) {
// no-op, the server should send nothing anyway
}
protected Credentials getCredentials() {
return new Credentials(
config.getString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME).toCharArray(),
config.getString(DefaultDriverOption.AUTH_PROVIDER_PASSWORD).toCharArray());
}
}
@@ -0,0 +1,166 @@
/*
* Copyright DataStax, Inc.
*
* Licensed 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 com.datastax.oss.driver.internal.core.auth;

import com.datastax.oss.driver.api.core.auth.AuthProvider;
import com.datastax.oss.driver.api.core.auth.Authenticator;
import com.datastax.oss.driver.api.core.auth.SyncAuthenticator;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.api.core.session.Session;
import com.datastax.oss.driver.shaded.guava.common.base.Charsets;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Objects;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Common infrastructure for plain text auth providers.
*
* <p>This can be reused to write an implementation that retrieves the credentials from another
* source than the configuration.
*/
@ThreadSafe
public abstract class PlainTextAuthProviderBase implements AuthProvider {

private static final Logger LOG = LoggerFactory.getLogger(PlainTextAuthProviderBase.class);

private final String logPrefix;

/**
* @param logPrefix a string that will get prepended to the logs (this is used for discrimination
* when you have multiple driver instances executing in the same JVM). Built-in
* implementations fill this with {@link Session#getName()}.
*/
protected PlainTextAuthProviderBase(@NonNull String logPrefix) {
this.logPrefix = Objects.requireNonNull(logPrefix);
}

/**
* Retrieves the credentials from the underlying source.
*
* <p>This is invoked every time the driver opens a new connection.
*/
@NonNull
protected abstract Credentials getCredentials();

@NonNull
@Override
public Authenticator newAuthenticator(
@NonNull EndPoint endPoint, @NonNull String serverAuthenticator) {
return new PlainTextAuthenticator(getCredentials());
}

@Override
public void onMissingChallenge(@NonNull EndPoint endPoint) {
LOG.warn(
"[{}] {} did not send an authentication challenge; "
+ "This is suspicious because the driver expects authentication",
logPrefix,
endPoint);
}

@Override
public void close() {
// nothing to do
}

protected static class Credentials {

private final char[] username;
private final char[] password;

public Credentials(@NonNull char[] username, @NonNull char[] password) {
this.username = Objects.requireNonNull(username);
this.password = Objects.requireNonNull(password);
}

@NonNull
public char[] getUsername() {
return username;
}

@NonNull
public char[] getPassword() {
return password;
}

/** Clears the credentials from memory when they're no longer needed. */
protected void clear() {
// Note: this is a bit irrelevant with the built-in provider, because the config already
// caches the credentials in memory. But it might be useful for a custom implementation that
// retrieves the credentials from a different source.
Arrays.fill(getUsername(), (char) 0);
Arrays.fill(getPassword(), (char) 0);
}
}

protected static class PlainTextAuthenticator implements SyncAuthenticator {

private final ByteBuffer initialToken;

protected PlainTextAuthenticator(@NonNull Credentials credentials) {
Objects.requireNonNull(credentials);
ByteBuffer usernameBytes = toUtf8Bytes(credentials.getUsername());
ByteBuffer passwordBytes = toUtf8Bytes(credentials.getPassword());
credentials.clear();

this.initialToken =
ByteBuffer.allocate(usernameBytes.remaining() + passwordBytes.remaining() + 2);
initialToken.put((byte) 0);
initialToken.put(usernameBytes);
initialToken.put((byte) 0);
initialToken.put(passwordBytes);
initialToken.flip();

// Clear temporary buffers
usernameBytes.rewind();
while (usernameBytes.remaining() > 0) {
usernameBytes.put((byte) 0);
}
passwordBytes.rewind();
while (passwordBytes.remaining() > 0) {
passwordBytes.put((byte) 0);
}
}

private ByteBuffer toUtf8Bytes(char[] charArray) {
CharBuffer charBuffer = CharBuffer.wrap(charArray);
return Charsets.UTF_8.encode(charBuffer);
}

@Override
@Nullable
public ByteBuffer initialResponseSync() {
return initialToken.duplicate();
}

@Override
@Nullable
public ByteBuffer evaluateChallengeSync(@Nullable ByteBuffer token) {
return null;
}

@Override
public void onAuthenticationSuccessSync(@Nullable ByteBuffer token) {
// no-op, the server should send nothing anyway
}
}
}

0 comments on commit 7c37f32

Please sign in to comment.