From e10c40bb943e4f3433d788726e6c0b34adc42a7e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 13 Oct 2026 10:28:24 -0400 Subject: [PATCH 1/5] Restore deprecated DSE authentication API compatibility Restore public authentication signatures removed by #1038. Keep ordinary SASL PLAIN constructors, subclass hooks, and the BaseDseAuthenticator hierarchy usable, including empty authorization IDs. Reject nonempty proxy identities, DSE negotiation, GSSAPI, and legacy DSE configuration. Cover supported compatibility paths and rejected operations with focused tests, and preserve nullable unused GSSAPI parameters. Refs #1038 --- .../api/core/auth/BaseDseAuthenticator.java | 58 +++++ .../core/auth/DseGssApiAuthProviderBase.java | 188 +++++++++++++++ .../auth/DsePlainTextAuthProviderBase.java | 35 +++ .../ProgrammaticDseGssApiAuthProvider.java | 42 ++++ .../api/core/auth/ProxyAuthentication.java | 35 +++ .../api/core/config/DseDriverOption.java | 42 ++++ .../core/auth/PlainTextAuthProviderBase.java | 69 +++++- .../ProgrammaticPlainTextAuthProvider.java | 19 ++ .../api/core/config/TypedDriverOption.java | 20 ++ .../api/core/session/SessionBuilder.java | 24 ++ .../auth/DseAuthApiCompatibilityTest.java | 129 ++++++++++ ...ainTextAuthenticatorCompatibilityTest.java | 224 ++++++++++++++++++ 12 files changed, 882 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/com/datastax/dse/driver/api/core/auth/BaseDseAuthenticator.java create mode 100644 core/src/main/java/com/datastax/dse/driver/api/core/auth/DseGssApiAuthProviderBase.java create mode 100644 core/src/main/java/com/datastax/dse/driver/api/core/auth/DsePlainTextAuthProviderBase.java create mode 100644 core/src/main/java/com/datastax/dse/driver/api/core/auth/ProgrammaticDseGssApiAuthProvider.java create mode 100644 core/src/main/java/com/datastax/dse/driver/api/core/auth/ProxyAuthentication.java create mode 100644 core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java create mode 100644 core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java create mode 100644 core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/auth/BaseDseAuthenticator.java b/core/src/main/java/com/datastax/dse/driver/api/core/auth/BaseDseAuthenticator.java new file mode 100644 index 00000000000..fc4c03002c3 --- /dev/null +++ b/core/src/main/java/com/datastax/dse/driver/api/core/auth/BaseDseAuthenticator.java @@ -0,0 +1,58 @@ +/* + * 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 com.datastax.dse.driver.api.core.auth; + +import com.datastax.oss.driver.api.core.auth.SyncAuthenticator; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.nio.ByteBuffer; +import java.util.Objects; +import net.jcip.annotations.ThreadSafe; + +/** + * Base class retained for ordinary authentication by existing subclasses. + * + * @deprecated DSE authentication negotiation is no longer supported. + */ +@Deprecated +@ThreadSafe +public abstract class BaseDseAuthenticator implements SyncAuthenticator { + + protected BaseDseAuthenticator(@NonNull String serverAuthenticator) { + if (Objects.requireNonNull(serverAuthenticator) + .equals("com.datastax.bdp.cassandra.auth.DseAuthenticator")) { + throw new UnsupportedOperationException( + "DSE authentication negotiation is no longer supported"); + } + } + + @NonNull + protected abstract ByteBuffer getMechanism(); + + @NonNull + protected abstract ByteBuffer getInitialServerChallenge(); + + @Nullable + @Override + public ByteBuffer initialResponseSync() { + return evaluateChallengeSync(getInitialServerChallenge()); + } + + @Override + public void onAuthenticationSuccessSync(@Nullable ByteBuffer token) {} +} diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/auth/DseGssApiAuthProviderBase.java b/core/src/main/java/com/datastax/dse/driver/api/core/auth/DseGssApiAuthProviderBase.java new file mode 100644 index 00000000000..51cc6c4b031 --- /dev/null +++ b/core/src/main/java/com/datastax/dse/driver/api/core/auth/DseGssApiAuthProviderBase.java @@ -0,0 +1,188 @@ +/* + * 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 com.datastax.dse.driver.api.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.metadata.EndPoint; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.nio.ByteBuffer; +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.login.Configuration; +import net.jcip.annotations.Immutable; +import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; + +/** @deprecated DSE GSSAPI authentication is no longer supported. */ +@Deprecated +@ThreadSafe +public abstract class DseGssApiAuthProviderBase implements AuthProvider { + + public static final String DEFAULT_SASL_SERVICE_NAME = "dse"; + public static final String SASL_SERVICE_NAME_PROPERTY = "dse.sasl.service"; + + protected DseGssApiAuthProviderBase(@NonNull String logPrefix) { + throw unsupported(); + } + + @NonNull + protected abstract GssApiOptions getOptions( + @NonNull EndPoint endPoint, @NonNull String serverAuthenticator); + + @NonNull + @Override + public Authenticator newAuthenticator( + @NonNull EndPoint endPoint, @NonNull String serverAuthenticator) { + throw unsupported(); + } + + @Override + public void onMissingChallenge(@NonNull EndPoint endPoint) { + throw unsupported(); + } + + @Override + public void close() {} + + private static UnsupportedOperationException unsupported() { + return new UnsupportedOperationException("DSE GSSAPI authentication is no longer supported"); + } + + /** @deprecated DSE GSSAPI authentication is no longer supported. */ + @Deprecated + @Immutable + public static class GssApiOptions { + + private GssApiOptions() {} + + @NonNull + public static Builder builder() { + return new Builder(); + } + + @Nullable + public Configuration getLoginConfiguration() { + throw unsupported(); + } + + @Nullable + public Subject getSubject() { + throw unsupported(); + } + + @Nullable + public String getSaslProtocol() { + throw unsupported(); + } + + @Nullable + public String getAuthorizationId() { + throw unsupported(); + } + + @NonNull + public Map getSaslProperties() { + throw unsupported(); + } + + /** @deprecated DSE GSSAPI authentication is no longer supported. */ + @Deprecated + @NotThreadSafe + public static class Builder { + + public Builder() {} + + @NonNull + public Builder withLoginConfiguration(@Nullable Configuration loginConfiguration) { + throw unsupported(); + } + + @NonNull + public Builder withLoginConfiguration(@Nullable Map loginConfiguration) { + throw unsupported(); + } + + @NonNull + public Builder withSubject(@Nullable Subject subject) { + throw unsupported(); + } + + @NonNull + public Builder withSaslProtocol(@Nullable String saslProtocol) { + throw unsupported(); + } + + @NonNull + public Builder withAuthorizationId(@Nullable String authorizationId) { + throw unsupported(); + } + + @NonNull + public Builder addSaslProperty(@NonNull String name, @NonNull String value) { + throw unsupported(); + } + + @NonNull + public GssApiOptions build() { + throw unsupported(); + } + + @SuppressWarnings("DoNotCallSuggester") + public static Configuration fetchLoginConfiguration(@Nullable Map options) { + throw unsupported(); + } + } + } + + /** @deprecated DSE GSSAPI authentication is no longer supported. */ + @Deprecated + @ThreadSafe + protected static class GssApiAuthenticator extends BaseDseAuthenticator { + + // Keep the unused parameters for compatibility with existing subclasses. + @SuppressWarnings("unused") + protected GssApiAuthenticator( + @Nullable GssApiOptions options, + @Nullable EndPoint endPoint, + @Nullable String serverAuthenticator) { + // Do not validate the unused server name before rejecting unsupported GSSAPI. + super("org.apache.cassandra.auth.PasswordAuthenticator"); + throw unsupported(); + } + + @NonNull + @Override + protected ByteBuffer getMechanism() { + throw unsupported(); + } + + @NonNull + @Override + protected ByteBuffer getInitialServerChallenge() { + throw unsupported(); + } + + @Nullable + @Override + public ByteBuffer evaluateChallengeSync(@Nullable ByteBuffer challenge) { + throw unsupported(); + } + } +} diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/auth/DsePlainTextAuthProviderBase.java b/core/src/main/java/com/datastax/dse/driver/api/core/auth/DsePlainTextAuthProviderBase.java new file mode 100644 index 00000000000..8ffc4cc75c5 --- /dev/null +++ b/core/src/main/java/com/datastax/dse/driver/api/core/auth/DsePlainTextAuthProviderBase.java @@ -0,0 +1,35 @@ +/* + * 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 com.datastax.dse.driver.api.core.auth; + +import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase; +import edu.umd.cs.findbugs.annotations.NonNull; +import net.jcip.annotations.ThreadSafe; + +/** + * @deprecated Retained for source and binary compatibility. Use {@link PlainTextAuthProviderBase} + * for standard plaintext authentication. + */ +@Deprecated +@ThreadSafe +public abstract class DsePlainTextAuthProviderBase extends PlainTextAuthProviderBase { + + protected DsePlainTextAuthProviderBase(@NonNull String logPrefix) { + super(logPrefix); + } +} diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/auth/ProgrammaticDseGssApiAuthProvider.java b/core/src/main/java/com/datastax/dse/driver/api/core/auth/ProgrammaticDseGssApiAuthProvider.java new file mode 100644 index 00000000000..97b9f21fdcb --- /dev/null +++ b/core/src/main/java/com/datastax/dse/driver/api/core/auth/ProgrammaticDseGssApiAuthProvider.java @@ -0,0 +1,42 @@ +/* + * 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 com.datastax.dse.driver.api.core.auth; + +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import net.jcip.annotations.ThreadSafe; + +/** @deprecated DSE GSSAPI authentication is no longer supported. */ +@Deprecated +@ThreadSafe +public class ProgrammaticDseGssApiAuthProvider extends DseGssApiAuthProviderBase { + + // Keep the unused parameter for compatibility with existing callers. + @SuppressWarnings("unused") + public ProgrammaticDseGssApiAuthProvider(@Nullable GssApiOptions options) { + super(""); + } + + @NonNull + @Override + protected GssApiOptions getOptions( + @NonNull EndPoint endPoint, @NonNull String serverAuthenticator) { + throw new UnsupportedOperationException("DSE GSSAPI authentication is no longer supported"); + } +} diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/auth/ProxyAuthentication.java b/core/src/main/java/com/datastax/dse/driver/api/core/auth/ProxyAuthentication.java new file mode 100644 index 00000000000..86706d7ecfc --- /dev/null +++ b/core/src/main/java/com/datastax/dse/driver/api/core/auth/ProxyAuthentication.java @@ -0,0 +1,35 @@ +/* + * 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 com.datastax.dse.driver.api.core.auth; + +import com.datastax.oss.driver.api.core.cql.Statement; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** @deprecated DSE proxy execution is no longer supported. */ +@Deprecated +@SuppressWarnings("DoNotCallSuggester") +public class ProxyAuthentication { + + /** @deprecated DSE proxy execution is no longer supported. */ + @Deprecated + @NonNull + public static > StatementT executeAs( + @NonNull String userOrRole, @NonNull StatementT statement) { + throw new UnsupportedOperationException("DSE proxy execution is no longer supported"); + } +} diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java b/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java new file mode 100644 index 00000000000..e1196bee8d4 --- /dev/null +++ b/core/src/main/java/com/datastax/dse/driver/api/core/config/DseDriverOption.java @@ -0,0 +1,42 @@ +/* + * 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 com.datastax.dse.driver.api.core.config; + +import com.datastax.oss.driver.api.core.config.DriverOption; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** @deprecated DSE-specific configuration is no longer supported. */ +@Deprecated +public enum DseDriverOption implements DriverOption { + AUTH_PROVIDER_AUTHORIZATION_ID("advanced.auth-provider.authorization-id"), + AUTH_PROVIDER_SERVICE("advanced.auth-provider.service"), + AUTH_PROVIDER_LOGIN_CONFIGURATION("advanced.auth-provider.login-configuration"), + AUTH_PROVIDER_SASL_PROPERTIES("advanced.auth-provider.sasl-properties"); + + private final String path; + + DseDriverOption(String path) { + this.path = path; + } + + @NonNull + @Override + public String getPath() { + return path; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java index 3d73b200091..b7a7bbbc1a3 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java @@ -17,6 +17,7 @@ */ package com.datastax.oss.driver.api.core.auth; +import com.datastax.dse.driver.api.core.auth.BaseDseAuthenticator; 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; @@ -72,7 +73,8 @@ protected abstract Credentials getCredentials( public Authenticator newAuthenticator( @NonNull EndPoint endPoint, @NonNull String serverAuthenticator) throws AuthenticationException { - return new PlainTextAuthenticator(getCredentials(endPoint, serverAuthenticator), endPoint); + return new PlainTextAuthenticator( + getCredentials(endPoint, serverAuthenticator), endPoint, serverAuthenticator); } @Override @@ -100,6 +102,16 @@ public Credentials(@NonNull char[] username, @NonNull char[] password) { this.password = Objects.requireNonNull(password); } + /** @deprecated Only an empty authorization ID is supported for ordinary authentication. */ + @Deprecated + public Credentials( + @NonNull char[] username, @NonNull char[] password, @NonNull char[] authorizationId) { + this(username, password); + if (Objects.requireNonNull(authorizationId).length != 0) { + throw new UnsupportedOperationException("DSE proxy authentication is no longer supported"); + } + } + @NonNull public char[] getUsername() { return username; @@ -120,6 +132,17 @@ public char[] getPassword() { return password; } + /** + * Returns an empty authorization ID for ordinary authentication. + * + * @deprecated DSE proxy authentication is no longer supported. + */ + @Deprecated + @NonNull + public char[] getAuthorizationId() { + return new char[0]; + } + /** 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 @@ -130,7 +153,10 @@ protected void clear() { } } - protected static class PlainTextAuthenticator implements SyncAuthenticator { + protected static class PlainTextAuthenticator extends BaseDseAuthenticator { + + private static final ByteBuffer SERVER_INITIAL_CHALLENGE = + ByteBuffer.wrap("PLAIN-START".getBytes(Charsets.UTF_8)).asReadOnlyBuffer(); private static final EndPoint DUMMY_END_POINT = new EndPoint() { @@ -151,8 +177,26 @@ public String asMetricPrefix() { private final EndPoint endPoint; protected PlainTextAuthenticator(@NonNull Credentials credentials, @NonNull EndPoint endPoint) { + this(credentials, endPoint, "org.apache.cassandra.auth.PasswordAuthenticator"); + } + + /** + * Builds an authenticator for ordinary SASL PLAIN authentication. + * + * @deprecated DSE authentication negotiation is no longer supported. Ordinary server + * authenticators remain supported. + */ + @Deprecated + protected PlainTextAuthenticator( + @NonNull Credentials credentials, + @NonNull EndPoint endPoint, + @NonNull String serverAuthenticator) { + super(serverAuthenticator); Objects.requireNonNull(credentials); Objects.requireNonNull(endPoint); + if (Objects.requireNonNull(credentials.getAuthorizationId()).length != 0) { + throw new UnsupportedOperationException("DSE proxy authentication is no longer supported"); + } ByteBuffer username = toUtf8Bytes(credentials.getUsername()); ByteBuffer password = toUtf8Bytes(credentials.getPassword()); @@ -192,15 +236,34 @@ private static void clear(ByteBuffer buffer) { } } + /** @deprecated DSE authentication negotiation is no longer supported. */ + @Deprecated + @NonNull + @Override + public ByteBuffer getMechanism() { + return ByteBuffer.wrap("PLAIN".getBytes(Charsets.UTF_8)).asReadOnlyBuffer(); + } + + /** @deprecated DSE authentication negotiation is no longer supported. */ + @Deprecated + @NonNull + @Override + public ByteBuffer getInitialServerChallenge() { + return SERVER_INITIAL_CHALLENGE.asReadOnlyBuffer(); + } + @Nullable @Override public ByteBuffer initialResponseSync() { - return encodedCredentials; + return super.initialResponseSync(); } @Nullable @Override public ByteBuffer evaluateChallengeSync(@Nullable ByteBuffer challenge) { + if (SERVER_INITIAL_CHALLENGE.equals(challenge)) { + return encodedCredentials; + } throw new AuthenticationException(endPoint, "Incorrect challenge from server"); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProvider.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProvider.java index 14e1c8c6d94..60bb90fced8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProvider.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProvider.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.session.SessionBuilder; import com.datastax.oss.driver.internal.core.util.Strings; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; import net.jcip.annotations.ThreadSafe; /** @@ -66,6 +67,16 @@ public ProgrammaticPlainTextAuthProvider(@NonNull String username, @NonNull Stri this.password = Strings.requireNotEmpty(password, "password").toCharArray(); } + /** @deprecated Only an empty authorization ID is supported for ordinary authentication. */ + @Deprecated + public ProgrammaticPlainTextAuthProvider( + @NonNull String username, @NonNull String password, @NonNull String authorizationId) { + this(username, password); + if (!Objects.requireNonNull(authorizationId).isEmpty()) { + throw new UnsupportedOperationException("DSE proxy authentication is no longer supported"); + } + } + /** * Changes the username. * @@ -88,6 +99,14 @@ public void setPassword(@NonNull String password) { this.password = Strings.requireNotEmpty(password, "password").toCharArray(); } + /** @deprecated Only an empty authorization ID is supported for ordinary authentication. */ + @Deprecated + public void setAuthorizationId(@NonNull String authorizationId) { + if (!Objects.requireNonNull(authorizationId).isEmpty()) { + throw new UnsupportedOperationException("DSE proxy authentication is no longer supported"); + } + } + /** * {@inheritDoc} * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java index 8f88c78e482..a1b820de470 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java @@ -17,6 +17,7 @@ */ package com.datastax.oss.driver.api.core.config; +import com.datastax.dse.driver.api.core.config.DseDriverOption; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; @@ -721,6 +722,25 @@ public String toString() { /** The version of the application using the session. */ public static final TypedDriverOption APPLICATION_VERSION = new TypedDriverOption<>(DefaultDriverOption.APPLICATION_VERSION, GenericType.STRING); + /** @deprecated DSE proxy authentication is no longer supported. */ + @Deprecated + public static final TypedDriverOption AUTH_PROVIDER_AUTHORIZATION_ID = + new TypedDriverOption<>(DseDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID, GenericType.STRING); + /** @deprecated DSE GSSAPI authentication is no longer supported. */ + @Deprecated + public static final TypedDriverOption AUTH_PROVIDER_SERVICE = + new TypedDriverOption<>(DseDriverOption.AUTH_PROVIDER_SERVICE, GenericType.STRING); + /** @deprecated DSE GSSAPI authentication is no longer supported. */ + @Deprecated + public static final TypedDriverOption AUTH_PROVIDER_LOGIN_CONFIGURATION = + new TypedDriverOption<>( + DseDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION, GenericType.STRING); + /** @deprecated DSE GSSAPI authentication is no longer supported. */ + @Deprecated + public static final TypedDriverOption> AUTH_PROVIDER_SASL_PROPERTIES = + new TypedDriverOption<>( + DseDriverOption.AUTH_PROVIDER_SASL_PROPERTIES, + GenericType.mapOf(GenericType.STRING, GenericType.STRING)); /** The time after which the node level metrics will be evicted. */ public static final TypedDriverOption METRICS_NODE_EXPIRE_AFTER = new TypedDriverOption<>(DefaultDriverOption.METRICS_NODE_EXPIRE_AFTER, GenericType.DURATION); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 54069517df8..f145c34e172 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -23,6 +23,7 @@ */ package com.datastax.oss.driver.api.core.session; +import com.datastax.dse.driver.api.core.config.DseDriverOption; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.auth.AuthProvider; @@ -398,6 +399,15 @@ public SelfT withAuthCredentials(@NonNull String username, @NonNull String passw return withAuthProvider(new ProgrammaticPlainTextAuthProvider(username, password)); } + /** @deprecated Only an empty authorization ID is supported for ordinary authentication. */ + @Deprecated + @NonNull + public SelfT withAuthCredentials( + @NonNull String username, @NonNull String password, @NonNull String authorizationId) { + return withAuthProvider( + new ProgrammaticPlainTextAuthProvider(username, password, authorizationId)); + } + /** * @deprecated this method only exists to ease the transition from driver 3, it is an alias for * {@link #withAuthCredentials(String, String)}. @@ -408,6 +418,14 @@ public SelfT withCredentials(@NonNull String username, @NonNull String password) return withAuthCredentials(username, password); } + /** @deprecated Only an empty authorization ID is supported for ordinary authentication. */ + @Deprecated + @NonNull + public SelfT withCredentials( + @NonNull String username, @NonNull String password, @NonNull String authorizationId) { + return withAuthCredentials(username, password, authorizationId); + } + /** * Registers an SSL engine factory for the session. * @@ -809,6 +827,12 @@ protected final CompletionStage buildDefaultSessionAsync() { || defaultConfig.isDefined(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE)) { throw unsupportedAstra(); } + if (defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID) + || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_SERVICE) + || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION) + || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_SASL_PROPERTIES)) { + throw new UnsupportedOperationException("DSE authentication is no longer supported"); + } List configContactPoints = defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList()); diff --git a/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java b/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java new file mode 100644 index 00000000000..774ed52f497 --- /dev/null +++ b/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java @@ -0,0 +1,129 @@ +/* + * 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 com.datastax.dse.driver.api.core.auth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.datastax.dse.driver.api.core.config.DseDriverOption; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase; +import com.datastax.oss.driver.api.core.auth.ProgrammaticPlainTextAuthProvider; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.config.OptionsMap; +import com.datastax.oss.driver.api.core.config.TypedDriverOption; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import java.util.Map; +import org.junit.Test; + +@SuppressWarnings("deprecation") +public class DseAuthApiCompatibilityTest { + + @Test + public void should_keep_removed_credential_signatures_and_reject_proxy_authentication() { + assertThatThrownBy(() -> CqlSession.builder().withAuthCredentials("u", "p", "role")) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> CqlSession.builder().withCredentials("u", "p", "role")) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> new ProgrammaticPlainTextAuthProvider("u", "p", "role")) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + new PlainTextAuthProviderBase.Credentials( + new char[0], new char[0], "role".toCharArray())) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> ProxyAuthentication.executeAs("role", SimpleStatement.newInstance("SELECT 1"))) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + public void should_accept_empty_authorization_ids_for_ordinary_authentication() { + assertThat(CqlSession.builder().withAuthCredentials("u", "p", "")).isNotNull(); + assertThat(CqlSession.builder().withCredentials("u", "p", "")).isNotNull(); + ProgrammaticPlainTextAuthProvider provider = + new ProgrammaticPlainTextAuthProvider("u", "p", ""); + provider.setAuthorizationId(""); + assertThat( + new PlainTextAuthProviderBase.Credentials(new char[0], new char[0], new char[0]) + .getAuthorizationId()) + .isEmpty(); + assertThat( + new PlainTextAuthProviderBase.Credentials(new char[0], new char[0]) + .getAuthorizationId()) + .isEmpty(); + assertThatThrownBy(() -> provider.setAuthorizationId("role")) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + public void should_reject_null_authorization_ids() { + assertThatThrownBy(() -> CqlSession.builder().withAuthCredentials("u", "p", null)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> CqlSession.builder().withCredentials("u", "p", null)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new ProgrammaticPlainTextAuthProvider("u", "p", null)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy( + () -> new ProgrammaticPlainTextAuthProvider("u", "p").setAuthorizationId(null)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy( + () -> new PlainTextAuthProviderBase.Credentials(new char[0], new char[0], null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void should_keep_gssapi_options_and_reject_use() { + assertThat(DseGssApiAuthProviderBase.DEFAULT_SASL_SERVICE_NAME).isEqualTo("dse"); + assertThatThrownBy(() -> DseGssApiAuthProviderBase.GssApiOptions.builder().build()) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + DseGssApiAuthProviderBase.GssApiOptions.Builder.fetchLoginConfiguration( + Map.of("principal", "user"))) + .isInstanceOf(UnsupportedOperationException.class); + assertThat(TypedDriverOption.AUTH_PROVIDER_SERVICE.getRawOption()) + .isSameAs(DseDriverOption.AUTH_PROVIDER_SERVICE); + } + + @Test + public void should_reject_gssapi_authenticator_construction_for_any_server() { + assertThatThrownBy( + () -> + new DseGssApiAuthProviderBase.GssApiAuthenticator( + null, null, "org.apache.cassandra.auth.PasswordAuthenticator")) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> new DseGssApiAuthProviderBase.GssApiAuthenticator(null, null, null)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + public void should_reject_legacy_authentication_configuration_before_connecting() { + OptionsMap options = OptionsMap.driverDefaults(); + options.put(TypedDriverOption.AUTH_PROVIDER_SERVICE, "dse"); + + assertThatThrownBy( + () -> + CqlSession.builder() + .withConfigLoader(DriverConfigLoader.fromMap(options)) + .buildAsync() + .toCompletableFuture() + .join()) + .hasCauseInstanceOf(UnsupportedOperationException.class); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java new file mode 100644 index 00000000000..dc6d931d464 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java @@ -0,0 +1,224 @@ +/* + * 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 com.datastax.oss.driver.api.core.auth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import com.datastax.dse.driver.api.core.auth.BaseDseAuthenticator; +import com.datastax.dse.driver.api.core.auth.DsePlainTextAuthProviderBase; +import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase.Credentials; +import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase.PlainTextAuthenticator; +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.junit.Test; + +@SuppressWarnings("deprecation") +public class PlainTextAuthenticatorCompatibilityTest { + private static final String PASSWORD_AUTHENTICATOR = + "org.apache.cassandra.auth.PasswordAuthenticator"; + private static final String DSE_AUTHENTICATOR = + "com.datastax.bdp.cassandra.auth.DseAuthenticator"; + private final EndPoint endPoint = mock(EndPoint.class); + + @Test + public void should_preserve_all_plaintext_constructors_and_base_type() { + Credentials credentials = + new Credentials("user".toCharArray(), "pass".toCharArray(), new char[0]); + BaseDseAuthenticator[] authenticators = { + new PlainTextAuthenticator(credentials), + new PlainTextAuthenticator(credentials, endPoint), + new PlainTextAuthenticator(credentials, endPoint, PASSWORD_AUTHENTICATOR), + new PlainTextAuthenticator(credentials, endPoint, "custom.Authenticator") + }; + for (BaseDseAuthenticator authenticator : authenticators) { + assertThat(authenticator.initialResponseSync()).isEqualTo(plainResponse()); + authenticator.onAuthenticationSuccessSync(null); + assertThat(authenticator.evaluateChallengeSync(utf8("PLAIN-START"))) + .isEqualTo(plainResponse()); + assertThatThrownBy(() -> authenticator.evaluateChallengeSync(utf8("unexpected"))) + .isInstanceOf(AuthenticationException.class); + assertThatThrownBy(() -> authenticator.evaluateChallengeSync(null)) + .isInstanceOf(AuthenticationException.class); + } + } + + @Test + public void should_dispatch_initial_response_through_legacy_subclass_hooks() { + PlainTextAuthenticator authenticator = + new PlainTextAuthenticator( + new Credentials("user".toCharArray(), "pass".toCharArray()), + endPoint, + PASSWORD_AUTHENTICATOR) { + @Override + public ByteBuffer getInitialServerChallenge() { + return utf8("custom challenge"); + } + + @Override + public ByteBuffer evaluateChallengeSync(ByteBuffer challenge) { + assertThat(challenge).isEqualTo(utf8("custom challenge")); + return utf8("custom response"); + } + }; + assertThat(authenticator.initialResponseSync()).isEqualTo(utf8("custom response")); + } + + @Test + public void should_preserve_plain_hooks_with_independent_buffer_positions() { + PlainTextAuthenticator authenticator = + new PlainTextAuthenticator(new Credentials(new char[0], new char[0]), endPoint); + assertThat(authenticator.getMechanism()).isEqualTo(utf8("PLAIN")); + assertThat(authenticator.getInitialServerChallenge()).isEqualTo(utf8("PLAIN-START")); + authenticator.getMechanism().get(); + authenticator.getInitialServerChallenge().get(); + assertThat(authenticator.getMechanism()).isEqualTo(utf8("PLAIN")); + assertThat(authenticator.getInitialServerChallenge()).isEqualTo(utf8("PLAIN-START")); + } + + @Test + public void should_encode_ordinary_response_after_empty_authorization_id_updates() { + ProgrammaticPlainTextAuthProvider provider = + new ProgrammaticPlainTextAuthProvider("user", "pass", ""); + provider.setAuthorizationId(""); + assertThatThrownBy(() -> provider.setAuthorizationId("role")) + .isInstanceOf(UnsupportedOperationException.class); + assertThat( + provider + .newAuthenticator(endPoint, PASSWORD_AUTHENTICATOR) + .initialResponse() + .toCompletableFuture() + .join()) + .isEqualTo(plainResponse()); + } + + @Test + public void should_preserve_legacy_plain_provider_subclasses() { + DsePlainTextAuthProviderBase provider = + new DsePlainTextAuthProviderBase("") { + @Override + protected Credentials getCredentials(EndPoint endpoint, String serverAuthenticator) { + return new Credentials("user".toCharArray(), "pass".toCharArray(), new char[0]); + } + + @Override + public Authenticator newAuthenticator(EndPoint endpoint, String serverAuthenticator) { + return new PlainTextAuthenticator( + getCredentials(endpoint, serverAuthenticator), endpoint, serverAuthenticator); + } + }; + assertThat( + provider + .newAuthenticator(endPoint, PASSWORD_AUTHENTICATOR) + .initialResponse() + .toCompletableFuture() + .join()) + .isEqualTo(plainResponse()); + } + + @Test + public void should_reject_dse_server_authenticator() { + Credentials credentials = new Credentials("user".toCharArray(), "pass".toCharArray()); + assertThatThrownBy(() -> new PlainTextAuthenticator(credentials, endPoint, DSE_AUTHENTICATOR)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + new ProgrammaticPlainTextAuthProvider("user", "pass") + .newAuthenticator(endPoint, DSE_AUTHENTICATOR)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> new PlainTextAuthenticator(credentials, endPoint, null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void should_reject_custom_proxy_credentials_before_reading_password() { + Credentials credentials = + new Credentials(new char[0], new char[0]) { + @Override + public char[] getAuthorizationId() { + return "role".toCharArray(); + } + + @Override + public char[] getPassword() { + throw new AssertionError("Must reject the proxy identity before encoding credentials"); + } + }; + assertThatThrownBy(() -> new PlainTextAuthenticator(credentials)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> new PlainTextAuthenticator(credentials, endPoint)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> new PlainTextAuthenticator(credentials, endPoint, PASSWORD_AUTHENTICATOR)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + public void should_reject_null_authorization_id_from_custom_credentials() { + Credentials credentials = + new Credentials(new char[0], new char[0]) { + @Override + public char[] getAuthorizationId() { + return null; + } + }; + assertThatThrownBy(() -> new PlainTextAuthenticator(credentials, endPoint)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void should_preserve_ordinary_base_authenticator_behavior() { + BaseDseAuthenticator authenticator = new LegacyBaseAuthenticator(PASSWORD_AUTHENTICATOR); + assertThat(authenticator.initialResponseSync()).isEqualTo(plainResponse()); + authenticator.onAuthenticationSuccessSync(null); + assertThatThrownBy(() -> new LegacyBaseAuthenticator(DSE_AUTHENTICATOR)) + .isInstanceOf(UnsupportedOperationException.class); + } + + private static class LegacyBaseAuthenticator extends BaseDseAuthenticator { + LegacyBaseAuthenticator(String serverAuthenticator) { + super(serverAuthenticator); + } + + @Override + protected ByteBuffer getMechanism() { + return utf8("PLAIN"); + } + + @Override + protected ByteBuffer getInitialServerChallenge() { + return utf8("PLAIN-START"); + } + + @Override + public ByteBuffer evaluateChallengeSync(ByteBuffer challenge) { + assertThat(challenge).isEqualTo(utf8("PLAIN-START")); + return plainResponse(); + } + } + + private static ByteBuffer utf8(String value) { + return ByteBuffer.wrap(value.getBytes(StandardCharsets.UTF_8)); + } + + private static ByteBuffer plainResponse() { + return ByteBuffer.wrap(new byte[] {0, 'u', 's', 'e', 'r', 0, 'p', 'a', 's', 's'}); + } +} From 2dbf8fdf3155e3b7d8220c599cc96bb1892f60fc Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 13 Oct 2026 10:28:24 -0400 Subject: [PATCH 2/5] Export restored DSE authentication APIs from both bundles Expose the restored authentication and configuration packages to OSGi consumers of both core and core-shaded. The shaded artifact rebuilds its manifest independently and needs the same compatibility exports. --- core-shaded/pom.xml | 4 ++-- core/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 75f62a477bd..d872268d1ce 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -356,8 +356,8 @@ 1) The driver's packages (API and internal); 2) All shaded packages, except Guava which resides in a separate bundle. --> - - com.datastax.oss.driver.api.core.*, com.datastax.oss.driver.internal.core.*, com.datastax.dse.driver.api.core.cql.reactive, com.datastax.dse.driver.api.core.metadata.*, com.datastax.dse.driver.internal.core.cql.reactive, com.datastax.dse.driver.internal.core.util.concurrent, com.datastax.oss.driver.shaded.netty.*, com.datastax.oss.driver.shaded.fasterxml.jackson.*, + + com.datastax.oss.driver.api.core.*, com.datastax.oss.driver.internal.core.*, com.datastax.dse.driver.api.core.auth, com.datastax.dse.driver.api.core.config, com.datastax.dse.driver.api.core.cql.reactive, com.datastax.dse.driver.api.core.metadata.*, com.datastax.dse.driver.internal.core.cql.reactive, com.datastax.dse.driver.internal.core.util.concurrent, com.datastax.oss.driver.shaded.netty.*, com.datastax.oss.driver.shaded.fasterxml.jackson.*, true diff --git a/core/pom.xml b/core/pom.xml index ee7c10b7163..ce8c27b18ee 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -290,8 +290,8 @@ optional for dependencies declared as non-optional in the pom files. -->jnr.*;resolution:=optional, org.reactivestreams.*;resolution:=optional, reactor.blockhound.*;resolution:=optional, * - - com.datastax.oss.driver.*.core.*, com.datastax.dse.driver.api.core.cql.reactive, com.datastax.dse.driver.api.core.metadata.*, com.datastax.dse.driver.internal.core.cql.reactive, com.datastax.dse.driver.internal.core.util.concurrent + + com.datastax.oss.driver.*.core.*, com.datastax.dse.driver.api.core.auth, com.datastax.dse.driver.api.core.config, com.datastax.dse.driver.api.core.cql.reactive, com.datastax.dse.driver.api.core.metadata.*, com.datastax.dse.driver.internal.core.cql.reactive, com.datastax.dse.driver.internal.core.util.concurrent From 39421d6304275351e6a1b7617166d7c2bcb36ccd Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 13 Oct 2026 10:28:24 -0400 Subject: [PATCH 3/5] Document migration from restored DSE authentication APIs Explain ordinary PLAIN compatibility through legacy constructors, hooks, and empty authorization IDs. Distinguish unsupported proxy authentication, GSSAPI, and old configuration keys from the restored signatures, and keep removed Graph APIs and unrelated DSE options outside this restoration. Refs #1038 --- upgrade_guide/README.md | 49 ++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 59dae4cafbd..32c94aa32c8 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -25,8 +25,9 @@ under the License. The DSE protocol versions, continuous paging, Graph API shells, and geometry and DateRange types are no longer available. DSE metadata and query builder types remain as deprecated API shells; -their runtime support has been removed. Remove usages of `DseSession`, `DseDriverOption`, and the -Graph and continuous paging APIs before upgrading. +their runtime support has been removed. Remove usages of `DseSession`, unsupported `DseDriverOption` constants, and +the Graph and continuous paging APIs before upgrading. Deprecated DSE authentication option +constants remain available as described below. The generic reactive CQL and mapper APIs remain available under their existing `com.datastax.dse` package names. @@ -47,9 +48,9 @@ If `basic.load-balancing-policy.class` names `DseLoadBalancingPolicy` or `DseDcInferringLoadBalancingPolicy`, replace it with `DefaultLoadBalancingPolicy` or `DcInferringLoadBalancingPolicy`, respectively. The DSE policy classes have been removed. -Serialized `OptionsMap` instances from 4.19.2.1 or 4.19.2.2 that contain `DseDriverOption` entries -cannot be deserialized with this release because that enum has been removed. This includes every -map created with `OptionsMap.driverDefaults()` in those releases, even if the application did not +Serialized `OptionsMap` instances from 4.19.2.1 or 4.19.2.2 that contain removed +`DseDriverOption` constants cannot be deserialized with this release. This includes every map +created with `OptionsMap.driverDefaults()` in those releases, even if the application did not configure DSE features. Rebuild those maps with the remaining options before upgrading; the serialization compatibility noted under 4.19.2.2 below does not extend to these maps. @@ -64,7 +65,7 @@ execution. Configuring `advanced.auth-provider.class` as `DsePlainTextAuthProvid ordinary username and password authentication, use `PlainTextAuthProvider` instead of `DsePlainTextAuthProvider`. -The following APIs were removed: +The following APIs remain for deprecated compatibility: - `SessionBuilder.withAuthCredentials(username, password, authorizationId)` and the deprecated `SessionBuilder.withCredentials(username, password, authorizationId)` alias @@ -73,24 +74,32 @@ The following APIs were removed: - `PlainTextAuthProviderBase.Credentials(username, password, authorizationId)` and `getAuthorizationId()` - `DseDriverOption.AUTH_PROVIDER_*` and the DSE `TypedDriverOption.AUTH_PROVIDER_*` options -- `ProxyAuthentication`, `BaseDseAuthenticator`, `DseGssApiAuthProviderBase`, +- `ProxyAuthentication` for CQL statements, `BaseDseAuthenticator`, `DseGssApiAuthProviderBase`, `ProgrammaticDseGssApiAuthProvider`, and `DsePlainTextAuthProviderBase` -Removing these public signatures in 4.19.2.3 is an intentional exception to the -[public API compatibility convention](../manual/api_conventions/). Applications compiled against -them must migrate and be recompiled before upgrading. - -`PlainTextAuthProviderBase.PlainTextAuthenticator` now implements `SyncAuthenticator` directly -instead of extending `BaseDseAuthenticator`. Its three-argument constructor -`(Credentials, EndPoint, String)` was removed; custom subclasses should use -`(Credentials, EndPoint)`. The deprecated `(Credentials)` constructor remains for ordinary plaintext -subclasses, but DSE negotiation methods such as `getMechanism()` and `getInitialServerChallenge()` -are no longer available. +Calls that request DSE proxy authentication or GSSAPI throw `UnsupportedOperationException`. +The authorization-ID overloads and setter accept empty IDs for ordinary plaintext authentication +and reject nonempty proxy identities. Null IDs remain invalid. The credentials getter returns an +empty ID. Custom credentials that return a nonempty authorization ID are rejected before encoding +the authentication response. +Configuring their old option keys fails session creation before connecting. The +`DsePlainTextAuthProviderBase` alias remains available for ordinary SASL PLAIN authentication. +The Graph overload of `ProxyAuthentication.executeAs` remains unavailable with the removed Graph +API. + +`PlainTextAuthProviderBase.PlainTextAuthenticator` retains its `BaseDseAuthenticator` superclass +and its three-argument constructor `(Credentials, EndPoint, String)` for ordinary authentication. +The third argument is the server authenticator name. The hooks return `PLAIN` and `PLAIN-START`; +they do not enable DSE negotiation. `BaseDseAuthenticator` supports its ordinary authentication +path, but rejects the DSE authenticator name at construction. The plaintext provider also rejects +that server name. Custom subclasses can use `(Credentials, EndPoint)` for ordinary plaintext +authentication. The DSE-only `authorization-id`, `service`, `login-configuration`, and `sasl-properties` -authentication settings are no longer read. Remove them from application configuration. Custom -providers that extended `DsePlainTextAuthProviderBase` can extend `PlainTextAuthProviderBase` for -standard SASL PLAIN authentication; DSE proxy authorization and GSSAPI have no replacement here. +authentication settings are rejected at session creation. Remove them from application +configuration. Custom providers that extended `DsePlainTextAuthProviderBase` can extend +`PlainTextAuthProviderBase` for standard SASL PLAIN authentication; DSE proxy authorization and +GSSAPI have no replacement here. #### DataStax Astra secure-connect-bundle support was removed From d030db141a368acf2a09e8bb5e375acea3488f30 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 13 Oct 2026 14:00:12 -0400 Subject: [PATCH 4/5] Scope legacy authentication validation to the selected provider Keep custom provider options usable and honor programmatic provider precedence. Reject the DSE server authenticator through authentication error handling while preserving direct constructor rejection. Cover all restored option keys and the provider and connection failure paths. --- .../core/auth/PlainTextAuthProviderBase.java | 5 ++ .../api/core/session/SessionBuilder.java | 19 +++- .../auth/DseAuthApiCompatibilityTest.java | 87 +++++++++++++++++-- ...ainTextAuthenticatorCompatibilityTest.java | 2 +- ...ProgrammaticPlainTextAuthProviderTest.java | 15 ++++ .../core/channel/ProtocolInitHandlerTest.java | 29 +++++++ upgrade_guide/README.md | 6 +- 7 files changed, 152 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java index b7a7bbbc1a3..925ab881cb7 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthProviderBase.java @@ -73,6 +73,11 @@ protected abstract Credentials getCredentials( public Authenticator newAuthenticator( @NonNull EndPoint endPoint, @NonNull String serverAuthenticator) throws AuthenticationException { + if ("com.datastax.bdp.cassandra.auth.DseAuthenticator".equals(serverAuthenticator)) { + throw new AuthenticationException( + endPoint, + "DSE authentication negotiation is no longer supported: " + serverAuthenticator); + } return new PlainTextAuthenticator( getCredentials(endPoint, serverAuthenticator), endPoint, serverAuthenticator); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index f145c34e172..9dfebdded58 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -827,10 +827,21 @@ protected final CompletionStage buildDefaultSessionAsync() { || defaultConfig.isDefined(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE)) { throw unsupportedAstra(); } - if (defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID) - || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_SERVICE) - || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION) - || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_SASL_PROPERTIES)) { + AuthProvider authProvider = programmaticArguments.getAuthProvider(); + String authProviderClass = + authProvider == null + ? defaultConfig.getString(DefaultDriverOption.AUTH_PROVIDER_CLASS, "") + : authProvider.getClass().getName(); + boolean builtInPlainText = + authProviderClass.equals("PlainTextAuthProvider") + || authProviderClass.equals( + "com.datastax.oss.driver.internal.core.auth.PlainTextAuthProvider") + || authProviderClass.equals(ProgrammaticPlainTextAuthProvider.class.getName()); + if (builtInPlainText + && (defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID) + || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_SERVICE) + || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION) + || defaultConfig.isDefined(DseDriverOption.AUTH_PROVIDER_SASL_PROPERTIES))) { throw new UnsupportedOperationException("DSE authentication is no longer supported"); } List configContactPoints = diff --git a/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java b/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java index 774ed52f497..a05a99ec821 100644 --- a/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java +++ b/core/src/test/java/com/datastax/dse/driver/api/core/auth/DseAuthApiCompatibilityTest.java @@ -19,15 +19,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import com.datastax.dse.driver.api.core.config.DseDriverOption; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase; import com.datastax.oss.driver.api.core.auth.ProgrammaticPlainTextAuthProvider; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.OptionsMap; import com.datastax.oss.driver.api.core.config.TypedDriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.session.ProgrammaticArguments; +import com.datastax.oss.driver.api.core.session.SessionBuilder; +import java.util.List; import java.util.Map; import org.junit.Test; @@ -113,17 +119,88 @@ public void should_reject_gssapi_authenticator_construction_for_any_server() { } @Test - public void should_reject_legacy_authentication_configuration_before_connecting() { - OptionsMap options = OptionsMap.driverDefaults(); - options.put(TypedDriverOption.AUTH_PROVIDER_SERVICE, "dse"); + public void should_reject_all_legacy_options_for_builtin_plaintext_providers() { + for (String providerClass : + List.of( + "PlainTextAuthProvider", + "com.datastax.oss.driver.internal.core.auth.PlainTextAuthProvider")) { + for (OptionsMap options : legacyOptions()) { + options.put(TypedDriverOption.AUTH_PROVIDER_CLASS, providerClass); + assertThatThrownBy( + () -> + new ValidationBuilder() + .withConfigLoader(DriverConfigLoader.fromMap(options)) + .buildAsync() + .toCompletableFuture() + .join()) + .hasCauseInstanceOf(UnsupportedOperationException.class); + } + } + for (OptionsMap options : legacyOptions()) { + options.put(TypedDriverOption.AUTH_PROVIDER_CLASS, "custom.Provider"); + assertThatThrownBy( + () -> + new ValidationBuilder() + .withAuthCredentials("u", "p") + .withConfigLoader(DriverConfigLoader.fromMap(options)) + .buildAsync() + .toCompletableFuture() + .join()) + .hasCauseInstanceOf(UnsupportedOperationException.class); + } + } + + @Test + public void should_allow_custom_providers_to_own_legacy_option_names() { + for (OptionsMap options : legacyOptions()) { + options.put(TypedDriverOption.AUTH_PROVIDER_CLASS, "custom.Provider"); + assertValidationPasses(new ValidationBuilder(), options); + options.put(TypedDriverOption.AUTH_PROVIDER_CLASS, "PlainTextAuthProvider"); + assertValidationPasses( + new ValidationBuilder().withAuthProvider(mock(AuthProvider.class)), options); + assertValidationPasses( + new ValidationBuilder() + .withAuthProvider(new ProgrammaticPlainTextAuthProvider("u", "p") {}), + options); + } + } + + private static List legacyOptions() { + OptionsMap service = OptionsMap.driverDefaults(); + service.put(TypedDriverOption.AUTH_PROVIDER_SERVICE, "dse"); + OptionsMap authorizationId = OptionsMap.driverDefaults(); + authorizationId.put(TypedDriverOption.AUTH_PROVIDER_AUTHORIZATION_ID, "role"); + OptionsMap login = OptionsMap.driverDefaults(); + login.put(TypedDriverOption.AUTH_PROVIDER_LOGIN_CONFIGURATION, "login"); + OptionsMap sasl = OptionsMap.driverDefaults(); + sasl.put(TypedDriverOption.AUTH_PROVIDER_SASL_PROPERTIES, Map.of("qop", "auth")); + return List.of(service, authorizationId, login, sasl); + } + private static void assertValidationPasses(ValidationBuilder builder, OptionsMap options) { assertThatThrownBy( () -> - CqlSession.builder() + builder .withConfigLoader(DriverConfigLoader.fromMap(options)) .buildAsync() .toCompletableFuture() .join()) - .hasCauseInstanceOf(UnsupportedOperationException.class); + .hasCauseInstanceOf(ValidationPassed.class); + } + + private static class ValidationPassed extends RuntimeException {} + + private static class ValidationBuilder extends SessionBuilder { + @Override + protected DriverContext buildContext( + DriverConfigLoader loader, ProgrammaticArguments arguments) { + // Stop after configuration validation without allocating resources or opening connections. + throw new ValidationPassed(); + } + + @Override + protected CqlSession wrap(CqlSession session) { + return session; + } } } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java index dc6d931d464..f6c8f948b59 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/auth/PlainTextAuthenticatorCompatibilityTest.java @@ -142,7 +142,7 @@ public void should_reject_dse_server_authenticator() { () -> new ProgrammaticPlainTextAuthProvider("user", "pass") .newAuthenticator(endPoint, DSE_AUTHENTICATOR)) - .isInstanceOf(UnsupportedOperationException.class); + .isInstanceOf(AuthenticationException.class); assertThatThrownBy(() -> new PlainTextAuthenticator(credentials, endPoint, null)) .isInstanceOf(NullPointerException.class); } diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProviderTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProviderTest.java index 04a87f7c44b..e5f3a185ca9 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProviderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/auth/ProgrammaticPlainTextAuthProviderTest.java @@ -18,6 +18,7 @@ package com.datastax.oss.driver.api.core.auth; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase.Credentials; import com.datastax.oss.driver.api.core.metadata.EndPoint; @@ -32,6 +33,20 @@ public class ProgrammaticPlainTextAuthProviderTest { @Mock private EndPoint endpoint; + @Test + public void should_report_dse_rejection_as_authentication_failure() { + String serverAuthenticator = "com.datastax.bdp.cassandra.auth.DseAuthenticator"; + assertThatThrownBy( + () -> + new ProgrammaticPlainTextAuthProvider("u", "p") + .newAuthenticator(endpoint, serverAuthenticator)) + .isInstanceOf(AuthenticationException.class) + .hasMessageContaining(serverAuthenticator) + .satisfies( + error -> + assertThat(((AuthenticationException) error).getEndPoint()).isSameAs(endpoint)); + } + @Test public void should_encode_standard_sasl_plain_response() { ProgrammaticPlainTextAuthProvider provider = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java index 310d21fe294..0e7ac237dbf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.api.core.InvalidKeyspaceException; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.auth.AuthenticationException; +import com.datastax.oss.driver.api.core.auth.ProgrammaticPlainTextAuthProvider; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfig; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; @@ -460,6 +461,34 @@ public void should_invoke_auth_provider_when_server_does_not_send_challenge() { assertThat(connectFuture).isSuccess(); } + @Test + public void should_preserve_plaintext_dse_rejection_as_authentication_error() { + channel + .pipeline() + .addLast( + ChannelFactory.INIT_HANDLER_NAME, + new ProtocolInitHandler( + internalDriverContext, + DefaultProtocolVersion.V4, + null, + END_POINT, + DriverChannelOptions.DEFAULT, + heartbeatHandler, + false)); + when(internalDriverContext.getAuthProvider()) + .thenReturn(Optional.of(new ProgrammaticPlainTextAuthProvider("u", "p"))); + ChannelFuture connectFuture = channel.connect(new InetSocketAddress("localhost", 9042)); + Frame requestFrame = readOutboundFrame(); + String serverAuthenticator = "com.datastax.bdp.cassandra.auth.DseAuthenticator"; + writeInboundFrame(requestFrame, new Authenticate(serverAuthenticator)); + assertThat(connectFuture) + .isFailed( + error -> + assertThat(error) + .isInstanceOf(AuthenticationException.class) + .hasMessageContaining(serverAuthenticator)); + } + @Test public void should_fail_to_initialize_if_server_sends_auth_error() { channel diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index 32c94aa32c8..f0fb7b1d207 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -78,11 +78,15 @@ The following APIs remain for deprecated compatibility: `ProgrammaticDseGssApiAuthProvider`, and `DsePlainTextAuthProviderBase` Calls that request DSE proxy authentication or GSSAPI throw `UnsupportedOperationException`. +The plaintext provider rejects servers announcing the DSE authenticator with +`AuthenticationException`, identifying the endpoint and unsupported authenticator. The authorization-ID overloads and setter accept empty IDs for ordinary plaintext authentication and reject nonempty proxy identities. Null IDs remain invalid. The credentials getter returns an empty ID. Custom credentials that return a nonempty authorization ID are rejected before encoding the authentication response. -Configuring their old option keys fails session creation before connecting. The +Configuring their old option keys with built-in plaintext authentication fails session creation +before connecting. Custom authentication providers may use these names for their own options; a +programmatic provider takes precedence over the configured provider class. The `DsePlainTextAuthProviderBase` alias remains available for ordinary SASL PLAIN authentication. The Graph overload of `ProxyAuthentication.executeAs` remains unavailable with the removed Graph API. From c876bd962f4e574a31a621181cb561a1e5d880c5 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 13 Oct 2026 14:04:53 -0400 Subject: [PATCH 5/5] Clarify legacy option ownership in migration guidance --- upgrade_guide/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/upgrade_guide/README.md b/upgrade_guide/README.md index f0fb7b1d207..28f9419a4d9 100644 --- a/upgrade_guide/README.md +++ b/upgrade_guide/README.md @@ -99,9 +99,10 @@ path, but rejects the DSE authenticator name at construction. The plaintext prov that server name. Custom subclasses can use `(Credentials, EndPoint)` for ordinary plaintext authentication. -The DSE-only `authorization-id`, `service`, `login-configuration`, and `sasl-properties` -authentication settings are rejected at session creation. Remove them from application -configuration. Custom providers that extended `DsePlainTextAuthProviderBase` can extend +The legacy `authorization-id`, `service`, `login-configuration`, and `sasl-properties` +authentication settings are rejected at session creation when using built-in plaintext providers. +Remove them when using those providers; custom providers may define their own meanings for these +keys. Custom providers that extended `DsePlainTextAuthProviderBase` can extend `PlainTextAuthProviderBase` for standard SASL PLAIN authentication; DSE proxy authorization and GSSAPI have no replacement here.