Skip to content

Commit

Permalink
Handle scope replacement correctly.
Browse files Browse the repository at this point in the history
'google.auth.credentials.with_scopes_if_required' doesn't replace scopes,
but only adds them if missing.

Addresses:
#3722 (comment).
  • Loading branch information
tseaver committed Aug 7, 2017
1 parent f2cdf6d commit cf5b2df
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 11 deletions.
8 changes: 4 additions & 4 deletions spanner/google/cloud/spanner/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ def ddl_statements(self):
def spanner_api(self):
"""Helper for session-related API calls."""
if self._spanner_api is None:
base_creds = self._instance._client.credentials
scoped = google.auth.credentials.with_scopes_if_required(
base_creds, (SPANNER_DATA_SCOPE,))
credentials = self._instance._client.credentials
if isinstance(credentials, google.auth.credentials.Scoped):
credentials = credentials.with_scopes((SPANNER_DATA_SCOPE,))
self._spanner_api = SpannerClient(
lib_name='gccl',
lib_version=__version__,
credentials=scoped,
credentials=credentials,
)
return self._spanner_api

Expand Down
53 changes: 46 additions & 7 deletions spanner/tests/unit/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,9 @@ def test_name_property(self):
expected_name = self.DATABASE_NAME
self.assertEqual(database.name, expected_name)

def test_spanner_api_property(self):
from google.cloud.spanner.database import SPANNER_DATA_SCOPE

expected_scopes = (SPANNER_DATA_SCOPE,)
def test_spanner_api_property_w_scopeless_creds(self):
client = _Client()
credentials = client.credentials = _make_credentials()
credentials = client.credentials = object()
instance = _Instance(self.INSTANCE_NAME, client=client)
pool = _Pool()
database = self._make_one(self.DATABASE_ID, instance, pool=pool)
Expand All @@ -213,9 +210,51 @@ def test_spanner_api_property(self):
spanner_client.assert_called_once_with(
lib_name='gccl',
lib_version=__version__,
credentials=credentials.with_scopes.return_value)
credentials=credentials)

def test_spanner_api_w_scoped_creds(self):
import google.auth.credentials
from google.cloud.spanner.database import SPANNER_DATA_SCOPE

class _CredentialsWithScopes(
google.auth.credentials.Scoped):

def __init__(self, scopes=(), source=None):
self._scopes = scopes
self._source = source

def requires_scopes(self):
return True

def with_scopes(self, scopes):
return self.__class__(scopes, self)

expected_scopes = (SPANNER_DATA_SCOPE,)
client = _Client()
credentials = client.credentials = _CredentialsWithScopes()
instance = _Instance(self.INSTANCE_NAME, client=client)
pool = _Pool()
database = self._make_one(self.DATABASE_ID, instance, pool=pool)

patch = mock.patch('google.cloud.spanner.database.SpannerClient')

with patch as spanner_client:
api = database.spanner_api

self.assertIs(api, spanner_client.return_value)

# API instance is cached
again = database.spanner_api
self.assertIs(again, api)

credentials.with_scopes.assert_called_once_with(expected_scopes)
self.assertEqual(len(spanner_client.call_args_list), 1)
called_args, called_kw = spanner_client.call_args
self.assertEqual(called_args, ())
self.assertEqual(called_kw['lib_name'], 'gccl')
self.assertEqual(called_kw['lib_version'], __version__)
scoped = called_kw['credentials']
self.assertEqual(scoped._scopes, expected_scopes)
self.assertIs(scoped._source, credentials)

def test___eq__(self):
instance = _Instance(self.INSTANCE_NAME)
Expand Down

0 comments on commit cf5b2df

Please sign in to comment.