Skip to content
Open
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
10 changes: 10 additions & 0 deletions java-spanner/google-cloud-spanner/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,16 @@
<version>2.94.0-SNAPSHOT</version><!-- {x-version-update:proto-google-cloud-trace-v1:current} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>
<dependency>
<groupId>com.google.crypto.tink</groupId>
<artifactId>tink</artifactId>
<version>1.13.0</version>
</dependency>
</dependencies>
<profiles>
<profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import com.google.cloud.spanner.admin.database.v1.stub.DatabaseAdminStubSettings;
import com.google.cloud.spanner.admin.instance.v1.InstanceAdminSettings;
import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStubSettings;
import com.google.cloud.spanner.omni.SpannerOmniCredentials;
import com.google.cloud.spanner.spi.SpannerRpcFactory;
import com.google.cloud.spanner.spi.v1.ChannelEndpointCacheFactory;
import com.google.cloud.spanner.spi.v1.GapicSpannerRpc;
Expand All @@ -66,6 +67,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.crypto.tink.util.SecretBytes;
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteSqlRequest;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
Expand Down Expand Up @@ -1239,9 +1241,20 @@ private static Builder prepareBuilder(Builder builder) {
builder.sessionPoolOptions =
builder.sessionPoolOptions.toBuilder().setExperimentalHost().build();
}
if (builder.credentials == null) {
if (builder.username != null && builder.secretBytes != null) {
builder.setCredentials(
new SpannerOmniCredentials(builder.username, builder.secretBytes, builder.host));
} else if (builder.credentials == null) {
builder.setCredentials(environment.getDefaultSpannerOmniCredentials());
}
if (builder.credentials instanceof SpannerOmniCredentials) {
((SpannerOmniCredentials) builder.credentials)
.initChannel(builder.usePlainText, builder.mTLSContext);
}
} else {
if (builder.username != null || builder.secretBytes != null) {
throw new IllegalStateException("login() can only be used with InstanceType.OMNI.");
}
}
return builder;
}
Expand Down Expand Up @@ -1296,6 +1309,8 @@ private static Builder prepareBuilder(Builder builder) {
DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS;
private boolean autoThrottleAdministrativeRequests = false;
private boolean trackTransactionStarter = false;
private String username;
private SecretBytes secretBytes;
private Map<DatabaseId, QueryOptions> defaultQueryOptions = new HashMap<>();
private boolean enableGrpcGcpOtelMetrics =
SpannerOptions.environment.isEnableGrpcGcpOtelMetrics();
Expand Down Expand Up @@ -1910,6 +1925,28 @@ public Builder setType(InstanceType instanceType) {
return this;
}

/**
* Authenticates to Spanner Omni using the provided username and password, and configures the
* resulting token for use in subsequent Spanner API calls.
*
* <p>Note: The provided {@code password} array will be cleared (zeroed out) by this method for
* security purposes.
*
* @param username The username for login.
* @param password The password for login.
* @return this builder
*/
public Builder login(String username, char[] password) {
Preconditions.checkArgument(
username != null && !username.isEmpty(), "username cannot be null or empty");
Preconditions.checkArgument(
password != null && password.length > 0, "password cannot be null or empty");

this.username = username;
this.secretBytes = SpannerOmniCredentials.convertToSecretBytes(password);
return this;
}

/** Enables gRPC-GCP extension with the default settings. This option is enabled by default. */
public Builder enableGrpcGcpExtension() {
return this.enableGrpcGcpExtension(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.connection.ClientSideStatementValueConverters.GrpcInterceptorProviderConverter;
import com.google.cloud.spanner.connection.StatementExecutor.StatementExecutorType;
import com.google.cloud.spanner.omni.SpannerOmniCredentials;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.crypto.tink.util.SecretBytes;
import io.grpc.Deadline;
import io.grpc.Deadline.Ticker;
import io.opentelemetry.api.OpenTelemetry;
Expand Down Expand Up @@ -154,6 +156,8 @@ public class ConnectionOptions {
static final boolean DEFAULT_USE_PLAIN_TEXT = false;
static final boolean DEFAULT_IS_EXPERIMENTAL_HOST = false;
static final SpannerOptions.InstanceType DEFAULT_TYPE = SpannerOptions.InstanceType.CLOUD;
static final String DEFAULT_USERNAME = "";
static final String DEFAULT_PASSWORD = "";
static final boolean DEFAULT_AUTOCOMMIT = true;
static final boolean DEFAULT_READONLY = false;
static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true;
Expand Down Expand Up @@ -224,6 +228,12 @@ public class ConnectionOptions {
/** The type of Spanner instance to connect to (cloud, omni, or emulator). */
public static final String TYPE_PROPERTY_NAME = "type";

/** Username for OPAQUE login */
public static final String USERNAME_PROPERTY_NAME = "username";

/** Password for OPAQUE login */
public static final String PASSWORD_PROPERTY_NAME = "password";

/** Client certificate path to establish mTLS */
static final String CLIENT_CERTIFICATE_PROPERTY_NAME = "clientCertificate";

Expand Down Expand Up @@ -775,6 +785,8 @@ private ConnectionOptions(Builder builder) {
System.getenv());
GoogleCredentials defaultSpannerOmniCredentials =
SpannerOptions.getDefaultSpannerOmniCredentialsFromSysEnv();
String username = getInitialConnectionPropertyValue(ConnectionProperties.USERNAME);
String password = getInitialConnectionPropertyValue(ConnectionProperties.PASSWORD);
// Using credentials on a plain text connection is not allowed, so if the user has not specified
// any credentials and is using a plain text connection, we should not try to get the
// credentials from the environment, but default to NoCredentials.
Expand All @@ -783,12 +795,18 @@ && getInitialConnectionPropertyValue(CREDENTIALS_URL) == null
&& getInitialConnectionPropertyValue(ENCODED_CREDENTIALS) == null
&& getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER) == null
&& getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
&& Strings.isNullOrEmpty(getInitialConnectionPropertyValue(ConnectionProperties.USERNAME))
&& usePlainText) {
this.credentials = NoCredentials.getInstance();
} else if (getInitialConnectionPropertyValue(OAUTH_TOKEN) != null) {
this.credentials =
new GoogleCredentials(
new AccessToken(getInitialConnectionPropertyValue(OAUTH_TOKEN), null));
} else if ((isSpannerOmniPattern || isSpannerOmni())
&& !Strings.isNullOrEmpty(username)
&& !Strings.isNullOrEmpty(password)) {
SecretBytes secretBytes = SpannerOmniCredentials.convertToSecretBytes(password.toCharArray());
this.credentials = new SpannerOmniCredentials(username, secretBytes, this.host);
} else if ((isSpannerOmniPattern || isSpannerOmni()) && defaultSpannerOmniCredentials != null) {
this.credentials = defaultSpannerOmniCredentials;
} else if (getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER) != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ public class ConnectionProperties {
},
InstanceTypeConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> USERNAME =
create(
ConnectionOptions.USERNAME_PROPERTY_NAME,
"The username to use for OPAQUE login.",
ConnectionOptions.DEFAULT_USERNAME,
StringValueConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> PASSWORD =
create(
ConnectionOptions.PASSWORD_PROPERTY_NAME,
"The password to use for OPAQUE login.",
ConnectionOptions.DEFAULT_PASSWORD,
StringValueConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> CLIENT_CERTIFICATE =
create(
CLIENT_CERTIFICATE_PROPERTY_NAME,
Expand Down
Loading
Loading