Skip to content

Commit 4381ad5

Browse files
authored
fix: move to using insecure grpc channels with emulator (#402)
* fix: move to using insecure grpc channels with emulator * chore: format * fix: add code to manually inject the id token on an insecure channel * chore: add line for comment * test: use the correct credentials object in mock * chore: black * chore: unused var * always configure the bearer token, even if not available * test: test the path populating an id token * chore: remove unused code and testing of unused code * chore: remove some code repetition * chore: feedback
1 parent 58300d3 commit 4381ad5

File tree

2 files changed

+35
-57
lines changed

2 files changed

+35
-57
lines changed

google/cloud/firestore_v1/base_client.py

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -167,50 +167,26 @@ def _firestore_api_helper(self, transport, client_class, client_module) -> Any:
167167

168168
def _emulator_channel(self, transport):
169169
"""
170-
Creates a channel using self._credentials in a similar way to grpc.secure_channel but
171-
using grpc.local_channel_credentials() rather than grpc.ssh_channel_credentials() to allow easy connection
172-
to a local firestore emulator. This allows local testing of firestore rules if the credentials have been
173-
created from a signed custom token.
170+
Creates an insecure channel to communicate with the local emulator.
171+
If credentials are provided the token is extracted and added to the
172+
headers. This supports local testing of firestore rules if the credentials
173+
have been created from a signed custom token.
174174
175175
:return: grpc.Channel or grpc.aio.Channel
176176
"""
177-
# TODO: Implement a special credentials type for emulator and use
178-
# "transport.create_channel" to create gRPC channels once google-auth
179-
# extends it's allowed credentials types.
177+
# Insecure channels are used for the emulator as secure channels
178+
# cannot be used to communicate on some environments.
179+
# https://github.com/googleapis/python-firestore/issues/359
180+
# Default the token to a non-empty string, in this case "owner".
181+
token = "owner"
182+
if self._credentials is not None and self._credentials.id_token is not None:
183+
token = self._credentials.id_token
184+
options = [("Authorization", f"Bearer {token}")]
185+
180186
if "GrpcAsyncIOTransport" in str(transport.__name__):
181-
return grpc.aio.secure_channel(
182-
self._emulator_host, self._local_composite_credentials()
183-
)
187+
return grpc.aio.insecure_channel(self._emulator_host, options=options)
184188
else:
185-
return grpc.secure_channel(
186-
self._emulator_host, self._local_composite_credentials()
187-
)
188-
189-
def _local_composite_credentials(self):
190-
"""
191-
Creates the credentials for the local emulator channel
192-
:return: grpc.ChannelCredentials
193-
"""
194-
credentials = google.auth.credentials.with_scopes_if_required(
195-
self._credentials, None
196-
)
197-
request = google.auth.transport.requests.Request()
198-
199-
# Create the metadata plugin for inserting the authorization header.
200-
metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
201-
credentials, request
202-
)
203-
204-
# Create a set of grpc.CallCredentials using the metadata plugin.
205-
google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
206-
207-
# Using the local_credentials to allow connection to emulator
208-
local_credentials = grpc.local_channel_credentials()
209-
210-
# Combine the local credentials and the authorization credentials.
211-
return grpc.composite_channel_credentials(
212-
local_credentials, google_auth_credentials
213-
)
189+
return grpc.insecure_channel(self._emulator_host, options=options)
214190

215191
def _target_helper(self, client_class) -> str:
216192
"""Return the target (where the API is).

tests/unit/v1/test_base_client.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ def test_emulator_channel(self):
146146
)
147147

148148
emulator_host = "localhost:8081"
149+
credentials = _make_credentials()
150+
database = "quanta"
149151
with mock.patch("os.getenv") as getenv:
150152
getenv.return_value = emulator_host
151-
152-
credentials = _make_credentials()
153-
database = "quanta"
153+
credentials.id_token = None
154154
client = self._make_one(
155155
project=self.PROJECT, credentials=credentials, database=database
156156
)
@@ -160,21 +160,23 @@ def test_emulator_channel(self):
160160
self.assertTrue(isinstance(channel, grpc.Channel))
161161
channel = client._emulator_channel(FirestoreGrpcAsyncIOTransport)
162162
self.assertTrue(isinstance(channel, grpc.aio.Channel))
163-
# checks that the credentials are composite ones using a local channel from grpc
164-
composite_credentials = client._local_composite_credentials()
165-
self.assertTrue(isinstance(composite_credentials, grpc.ChannelCredentials))
166-
self.assertTrue(
167-
isinstance(
168-
composite_credentials._credentials._call_credentialses[0],
169-
grpc._cython.cygrpc.MetadataPluginCallCredentials,
163+
164+
# Verify that when credentials are provided with an id token it is used
165+
# for channel construction
166+
# NOTE: On windows, emulation requires an insecure channel. If this is
167+
# altered to use a secure channel, start by verifying that it still
168+
# works as expected on windows.
169+
with mock.patch("os.getenv") as getenv:
170+
getenv.return_value = emulator_host
171+
credentials.id_token = "test"
172+
client = self._make_one(
173+
project=self.PROJECT, credentials=credentials, database=database
170174
)
171-
)
172-
self.assertTrue(
173-
isinstance(
174-
composite_credentials._credentials._channel_credentials,
175-
grpc._cython.cygrpc.LocalChannelCredentials,
175+
with mock.patch("grpc.insecure_channel") as insecure_channel:
176+
channel = client._emulator_channel(FirestoreGrpcTransport)
177+
insecure_channel.assert_called_once_with(
178+
emulator_host, options=[("Authorization", "Bearer test")]
176179
)
177-
)
178180

179181
def test_field_path(self):
180182
klass = self._get_target_class()
@@ -392,9 +394,9 @@ def test_paths(self):
392394

393395

394396
def _make_credentials():
395-
import google.auth.credentials
397+
import google.oauth2.credentials
396398

397-
return mock.Mock(spec=google.auth.credentials.Credentials)
399+
return mock.Mock(spec=google.oauth2.credentials.Credentials)
398400

399401

400402
def _make_batch_response(**kwargs):

0 commit comments

Comments
 (0)