Skip to content

Commit 9273d06

Browse files
tseaverJon Wayne Parrott
authored andcommitted
Move read-only methods of 'Scoped' into new interface, 'ReadOnlyScoped'. (#195)
Not all subclasses of 'Scoped' can sanely implement 'with_scopes' (e.g, on GCE the scopes are hard-wired in when creating the GCE node). Make 'Scoped' derive from 'ReadOnlyScoped', adding the 'with_scopes' method. Make GCE's 'credentials' class derive from 'ReadOnlyScoped'. Closes #194.
1 parent 171b790 commit 9273d06

File tree

4 files changed

+53
-44
lines changed

4 files changed

+53
-44
lines changed

packages/google-auth/google/auth/compute_engine/credentials.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from google.auth.compute_engine import _metadata
2525

2626

27-
class Credentials(credentials.Scoped, credentials.Credentials):
27+
class Credentials(credentials.ReadOnnlyScoped, credentials.Credentials):
2828
"""Compute Engine Credentials.
2929
3030
These credentials use the Google Compute Engine metadata server to obtain
@@ -105,17 +105,3 @@ def service_account_email(self):
105105
def requires_scopes(self):
106106
"""False: Compute Engine credentials can not be scoped."""
107107
return False
108-
109-
def with_scopes(self, scopes):
110-
"""Unavailable, Compute Engine credentials can not be scoped.
111-
112-
Scopes can only be set at Compute Engine instance creation time.
113-
See the `Compute Engine authentication documentation`_ for details on
114-
how to configure instance scopes.
115-
116-
.. _Compute Engine authentication documentation:
117-
https://cloud.google.com/compute/docs/authentication#using
118-
"""
119-
raise NotImplementedError(
120-
'Compute Engine credentials can not set scopes. Scopes must be '
121-
'set when the Compute Engine instance is created.')

packages/google-auth/google/auth/credentials.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ def before_request(self, request, method, url, headers):
123123

124124

125125
@six.add_metaclass(abc.ABCMeta)
126-
class Scoped(object):
127-
"""Interface for scoped credentials.
126+
class ReadOnnlyScoped(object):
127+
"""Interface for credentials whose scopes can be queried.
128128
129129
OAuth 2.0-based credentials allow limiting access using scopes as described
130130
in `RFC6749 Section 3.3`_.
@@ -152,7 +152,7 @@ class Scoped(object):
152152
.. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
153153
"""
154154
def __init__(self):
155-
super(Scoped, self).__init__()
155+
super(ReadOnnlyScoped, self).__init__()
156156
self._scopes = None
157157

158158
@property
@@ -166,6 +166,46 @@ def requires_scopes(self):
166166
"""
167167
return False
168168

169+
def has_scopes(self, scopes):
170+
"""Checks if the credentials have the given scopes.
171+
172+
.. warning: This method is not guaranteed to be accurate if the
173+
credentials are :attr:`~Credentials.invalid`.
174+
175+
Returns:
176+
bool: True if the credentials have the given scopes.
177+
"""
178+
return set(scopes).issubset(set(self._scopes or []))
179+
180+
181+
class Scoped(ReadOnnlyScoped):
182+
"""Interface for credentials whose scopes can be replaced while copying.
183+
184+
OAuth 2.0-based credentials allow limiting access using scopes as described
185+
in `RFC6749 Section 3.3`_.
186+
If a credential class implements this interface then the credentials either
187+
use scopes in their implementation.
188+
189+
Some credentials require scopes in order to obtain a token. You can check
190+
if scoping is necessary with :attr:`requires_scopes`::
191+
192+
if credentials.requires_scopes:
193+
# Scoping is required.
194+
credentials = credentials.create_scoped(['one', 'two'])
195+
196+
Credentials that require scopes must either be constructed with scopes::
197+
198+
credentials = SomeScopedCredentials(scopes=['one', 'two'])
199+
200+
Or must copy an existing instance using :meth:`with_scopes`::
201+
202+
scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
203+
204+
Some credentials have scopes but do not allow or require scopes to be set,
205+
these credentials can be used as-is.
206+
207+
.. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
208+
"""
169209
@abc.abstractmethod
170210
def with_scopes(self, scopes):
171211
"""Create a copy of these credentials with the specified scopes.
@@ -180,17 +220,6 @@ def with_scopes(self, scopes):
180220
"""
181221
raise NotImplementedError('This class does not require scoping.')
182222

183-
def has_scopes(self, scopes):
184-
"""Checks if the credentials have the given scopes.
185-
186-
.. warning: This method is not guaranteed to be accurate if the
187-
credentials are :attr:`~Credentials.invalid`.
188-
189-
Returns:
190-
bool: True if the credentials have the given scopes.
191-
"""
192-
return set(scopes).issubset(set(self._scopes or []))
193-
194223

195224
def with_scopes_if_required(credentials, scopes):
196225
"""Creates a copy of the credentials with scopes if scoping is required.

packages/google-auth/tests/compute_engine/test_credentials.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,3 @@ def test_before_request_refreshes(self, get):
105105

106106
# Credentials should now be valid.
107107
assert self.credentials.valid
108-
109-
def test_with_scopes(self):
110-
with pytest.raises(NotImplementedError):
111-
self.credentials.with_scopes(['one', 'two'])

packages/google-auth/tests/test_credentials.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,20 @@ def test_before_request():
7777
assert headers['authorization'] == 'Bearer token'
7878

7979

80-
class ScopedCredentialsImpl(credentials.Scoped, CredentialsImpl):
80+
class ReadOnnlyScopedCredentialsImpl(credentials.ReadOnnlyScoped,
81+
CredentialsImpl):
8182
@property
8283
def requires_scopes(self):
83-
return super(ScopedCredentialsImpl, self).requires_scopes
84-
85-
def with_scopes(self, scopes):
86-
raise NotImplementedError
84+
return super(ReadOnnlyScopedCredentialsImpl, self).requires_scopes
8785

8886

89-
def test_scoped_credentials_constructor():
90-
credentials = ScopedCredentialsImpl()
87+
def test_readonly_scoped_credentials_constructor():
88+
credentials = ReadOnnlyScopedCredentialsImpl()
9189
assert credentials._scopes is None
9290

9391

94-
def test_scoped_credentials_scopes():
95-
credentials = ScopedCredentialsImpl()
92+
def test_readonly_scoped_credentials_scopes():
93+
credentials = ReadOnnlyScopedCredentialsImpl()
9694
credentials._scopes = ['one', 'two']
9795
assert credentials.scopes == ['one', 'two']
9896
assert credentials.has_scopes(['one'])
@@ -101,8 +99,8 @@ def test_scoped_credentials_scopes():
10199
assert not credentials.has_scopes(['three'])
102100

103101

104-
def test_scoped_credentials_requires_scopes():
105-
credentials = ScopedCredentialsImpl()
102+
def test_readonly_scoped_credentials_requires_scopes():
103+
credentials = ReadOnnlyScopedCredentialsImpl()
106104
assert not credentials.requires_scopes
107105

108106

0 commit comments

Comments
 (0)