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
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,8 @@ def connect(
instance_type=None,
data_boost_enabled=False,
auto_partition_mode=False,
username=None,
password=None,
**kwargs,
):
"""Creates a connection to a Google Cloud Spanner database.
Expand Down Expand Up @@ -909,6 +911,10 @@ def connect(
:param client_key: (Optional) The path to the client key file used for mTLS connection.
This is intended only for Spanner Omni endpoints.
This is mandatory if Spanner Omni requires an mTLS connection.
:type username: str
:param username: (Optional) Username for Spanner Omni authentication.
:type password: str
:param password: (Optional) Password for Spanner Omni authentication.
"""
if client is None:
client_info = ClientInfo(
Expand Down Expand Up @@ -956,7 +962,29 @@ def connect(
)

project = "default"
credentials = AnonymousCredentials()
has_username = username is not None
has_password = password is not None
if has_username != has_password:
raise ValueError(
"Both username and password must be specified for Omni authentication"
)
from google.cloud.spanner_v1.omni.credentials import (
SpannerOmniCredentials,
)

if has_username and has_password:
credentials = SpannerOmniCredentials(
username=username,
password=password,
target=host_endpoint,
use_plain_text=use_plain_text,
ca_certificate=ca_certificate,
client_certificate=client_certificate,
client_key=client_key,
)
else:
credentials = AnonymousCredentials()
Comment thread
sagnghos marked this conversation as resolved.

client_options = kwargs.get("client_options")
if client_options is None:
client_options = ClientOptions(api_endpoint=host_endpoint)
Expand All @@ -969,6 +997,12 @@ def connect(
client_options = copy.copy(client_options)
client_options.api_endpoint = host_endpoint

client_kwargs = {}
if username is not None:
client_kwargs["username"] = username
if password is not None:
client_kwargs["password"] = password

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The username and password should already be in credentials, we can probably skip this?


client = spanner.Client(
project=project,
credentials=credentials,
Expand All @@ -980,6 +1014,7 @@ def connect(
client_certificate=client_certificate,
client_key=client_key,
instance_type=instance_type,
**client_kwargs,
)
else:
if project is not None and client.project != project:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def _create_spanner_omni_transport(
client_certificate,
client_key,
interceptors=None,
credentials=None,
):
"""Creates a Spanner Omni transport in async mode.

Expand All @@ -87,6 +88,8 @@ def _create_spanner_omni_transport(
client_certificate (str): Path to the client certificate file for mTLS.
client_key (str): Path to the client key file for mTLS.
interceptors (list): Optional list of interceptors to add to the channel.
credentials (google.auth.credentials.Credentials, optional): Credentials
to use for authentication.

Returns:
object: An instance of the transport class created by `transport_factory`.
Expand All @@ -98,8 +101,25 @@ def _create_spanner_omni_transport(
from google.auth.credentials import AnonymousCredentials

channel = None
all_interceptors = list(interceptors) if interceptors is not None else []
if credentials is not None:
if hasattr(credentials, "create_async_auth_interceptors"):
all_interceptors.extend(credentials.create_async_auth_interceptors())
elif hasattr(credentials, "create_async_auth_interceptor"):
res = credentials.create_async_auth_interceptor()
if isinstance(res, (list, tuple)):
all_interceptors.extend(res)
else:
all_interceptors.append(res)
elif hasattr(credentials, "create_auth_interceptor"):
res = credentials.create_auth_interceptor(is_async=True)
if isinstance(res, (list, tuple)):
all_interceptors.extend(res)
else:
all_interceptors.append(res)

if use_plain_text:
channel = grpc.aio.insecure_channel(target=host, interceptors=interceptors)
channel = grpc.aio.insecure_channel(target=host, interceptors=all_interceptors)
elif ca_certificate:
with open(ca_certificate, "rb") as f:
ca_cert = f.read()
Expand All @@ -119,12 +139,17 @@ def _create_spanner_omni_transport(
)
else:
ssl_creds = grpc.ssl_channel_credentials(root_certificates=ca_cert)
channel = grpc.aio.secure_channel(host, ssl_creds, interceptors=interceptors)
channel = grpc.aio.secure_channel(
host, ssl_creds, interceptors=all_interceptors
)
else:
raise ValueError(
"TLS/mTLS connection requires ca_certificate to be set for Spanner Omni"
)
return transport_factory(channel=channel, credentials=AnonymousCredentials())
actual_credentials = (
credentials if credentials is not None else AnonymousCredentials()
)
return transport_factory(channel=channel, credentials=actual_credentials)


def _create_experimental_host_transport(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ def __init__(
client_certificate=None,
client_key=None,
instance_type=None,
username=None,
password=None,
):
self._emulator_host = _get_spanner_emulator_host()
self._use_plain_text = use_plain_text
Expand Down Expand Up @@ -353,10 +355,37 @@ def __init__(
self._ca_certificate = ca_certificate
self._client_certificate = client_certificate
self._client_key = client_key
credentials = AnonymousCredentials()
self._host = host_endpoint
has_username = username is not None
has_password = password is not None
if has_username != has_password:
raise ValueError(
"Both username and password must be specified for Omni authentication"
)
from google.cloud.spanner_v1.omni.credentials import (
SpannerOmniCredentials,
)

if has_username and has_password:
credentials = SpannerOmniCredentials(
username=username,
password=password,
target=host_endpoint,
use_plain_text=use_plain_text,
ca_certificate=ca_certificate,
client_certificate=client_certificate,
client_key=client_key,
)
elif not isinstance(credentials, SpannerOmniCredentials):
credentials = AnonymousCredentials()
disable_builtin_metrics = True
elif isinstance(credentials, AnonymousCredentials):
self._emulator_host = self._client_options.api_endpoint
else:
if username is not None or password is not None:
raise ValueError(
"username and password can only be used when instance_type='omni'."
)

# NOTE: This API has no use for the _http argument, but sending it
# will have no impact since the _http() @property only lazily
Expand Down Expand Up @@ -509,6 +538,7 @@ def instance_admin_api(self):
self._ca_certificate,
self._client_certificate,
self._client_key,
credentials=self.credentials,
)

else:
Expand All @@ -519,6 +549,7 @@ def instance_admin_api(self):
self._ca_certificate,
self._client_certificate,
self._client_key,
credentials=self.credentials,
)

self._instance_admin_api = InstanceAdminClient(
Expand Down Expand Up @@ -567,6 +598,7 @@ def database_admin_api(self):
self._ca_certificate,
self._client_certificate,
self._client_key,
credentials=self.credentials,
)

else:
Expand All @@ -577,6 +609,7 @@ def database_admin_api(self):
self._ca_certificate,
self._client_certificate,
self._client_key,
credentials=self.credentials,
)

self._database_admin_api = DatabaseAdminClient(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ def spanner_api(self):
client._ca_certificate,
client._client_certificate,
client._client_key,
credentials=client.credentials,
)
else:
transport = _create_spanner_omni_transport_sync(
Expand All @@ -522,6 +523,7 @@ def spanner_api(self):
client._ca_certificate,
client._client_certificate,
client._client_key,
credentials=client.credentials,
)
self._spanner_api = SpannerClient(
client_info=client_info,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def spanner_api(self):
client._client_certificate,
client._client_key,
self._interceptors,
credentials=client.credentials,
)
else:
transport = _create_spanner_omni_transport_sync(
Expand All @@ -148,6 +149,7 @@ def spanner_api(self):
client._client_certificate,
client._client_key,
self._interceptors,
credentials=client.credentials,
)
self._spanner_api = SpannerClient(
client_info=client_info,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ def _create_spanner_omni_transport(
client_certificate,
client_key,
interceptors=None,
credentials=None,
):
"""Creates a Spanner Omni transport.

Expand All @@ -1056,6 +1057,8 @@ def _create_spanner_omni_transport(
client_certificate (str): Path to the client certificate file for mTLS.
client_key (str): Path to the client key file for mTLS.
interceptors (list): Optional list of interceptors to add to the channel.
credentials (google.auth.credentials.Credentials, optional): Credentials
to use for authentication (e.g. `SpannerOmniCredentials`).

Returns:
object: An instance of the transport class created by `transport_factory`.
Expand All @@ -1067,6 +1070,10 @@ def _create_spanner_omni_transport(
from google.auth.credentials import AnonymousCredentials

channel = None
all_interceptors = list(interceptors) if interceptors is not None else []
if credentials is not None and hasattr(credentials, "create_auth_interceptor"):
all_interceptors.append(credentials.create_auth_interceptor())

if use_plain_text:
channel = grpc.insecure_channel(target=host)
elif ca_certificate:
Expand All @@ -1093,9 +1100,12 @@ def _create_spanner_omni_transport(
raise ValueError(
"TLS/mTLS connection requires ca_certificate to be set for Spanner Omni"
)
if interceptors is not None:
channel = grpc.intercept_channel(channel, *interceptors)
return transport_factory(channel=channel, credentials=AnonymousCredentials())
if all_interceptors:
channel = grpc.intercept_channel(channel, *all_interceptors)
actual_credentials = (
credentials if credentials is not None else AnonymousCredentials()
)
return transport_factory(channel=channel, credentials=actual_credentials)


def _create_experimental_host_transport(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ def __init__(
client_certificate=None,
client_key=None,
instance_type=None,
username=None,
password=None,
):
self._emulator_host = _get_spanner_emulator_host()
self._use_plain_text = use_plain_text
Expand Down Expand Up @@ -316,10 +318,37 @@ def __init__(
self._ca_certificate = ca_certificate
self._client_certificate = client_certificate
self._client_key = client_key
credentials = AnonymousCredentials()
self._host = host_endpoint
has_username = username is not None
has_password = password is not None
if has_username != has_password:
raise ValueError(
"Both username and password must be specified for Omni authentication"
)
from google.cloud.spanner_v1.omni.credentials import (
SpannerOmniCredentials,
)

if has_username and has_password:
credentials = SpannerOmniCredentials(
username=username,
password=password,
target=host_endpoint,
use_plain_text=use_plain_text,
ca_certificate=ca_certificate,
client_certificate=client_certificate,
client_key=client_key,
)
elif not isinstance(credentials, SpannerOmniCredentials):
credentials = AnonymousCredentials()
disable_builtin_metrics = True
elif isinstance(credentials, AnonymousCredentials):
self._emulator_host = self._client_options.api_endpoint
else:
if username is not None or password is not None:
raise ValueError(
"username and password can only be used when instance_type='omni'."
)
super(Client, self).__init__(
project=project,
credentials=credentials,
Expand Down Expand Up @@ -443,6 +472,7 @@ def instance_admin_api(self):
self._ca_certificate,
self._client_certificate,
self._client_key,
credentials=self.credentials,
)
self._instance_admin_api = InstanceAdminClient(
client_info=self._client_info,
Expand Down Expand Up @@ -481,6 +511,7 @@ def database_admin_api(self):
self._ca_certificate,
self._client_certificate,
self._client_key,
credentials=self.credentials,
)
self._database_admin_api = DatabaseAdminClient(
client_info=self._client_info,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ def spanner_api(self):
client._ca_certificate,
client._client_certificate,
client._client_key,
credentials=client.credentials,
)
self._spanner_api = SpannerClient(
client_info=client_info,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Google LLC
#
# 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.
#

"""Spanner Omni authentication and connection utilities."""

from google.cloud.spanner_v1.omni.credentials import SpannerOmniCredentials
from google.cloud.spanner_v1.omni.login_client import LoginClient
from google.cloud.spanner_v1.omni.opaque import UserAuthenticator

__all__ = (
"LoginClient",
"SpannerOmniCredentials",
"UserAuthenticator",
)
Loading
Loading