Skip to content

Commit a896d2a

Browse files
author
Jon Wayne Parrott
authored
Add google.auth._oauth2client - helpers for oauth2client migration (#70)
1 parent b9897dc commit a896d2a

File tree

4 files changed

+361
-0
lines changed

4 files changed

+361
-0
lines changed

google/auth/_oauth2client.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Copyright 2016 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Helpers for transitioning from oauth2client to google-auth.
16+
17+
.. warning::
18+
This module is private as it is intended to assist first-party downstream
19+
clients with the transition from oauth2client to google-auth.
20+
"""
21+
22+
from __future__ import absolute_import
23+
24+
from google.auth import _helpers
25+
import google.auth.app_engine
26+
import google.oauth2.credentials
27+
import google.oauth2.service_account
28+
29+
try:
30+
import oauth2client.client
31+
import oauth2client.contrib.gce
32+
import oauth2client.service_account
33+
except ImportError:
34+
raise ImportError('oauth2client is not installed.')
35+
36+
try:
37+
import oauth2client.contrib.appengine
38+
_HAS_APPENGINE = True
39+
except ImportError:
40+
_HAS_APPENGINE = False
41+
42+
43+
_CONVERT_ERROR_TMPL = (
44+
'Unable to convert {} to a google-auth credentials class.')
45+
46+
47+
def _convert_oauth2_credentials(credentials):
48+
"""Converts to :class:`google.oauth2.credentials.Credentials`.
49+
50+
Args:
51+
credentials (Union[oauth2client.client.OAuth2Credentials,
52+
oauth2client.client.GoogleCredentials]): The credentials to
53+
convert.
54+
55+
Returns:
56+
google.oauth2.credentials.Credentials: The converted credentials.
57+
"""
58+
new_credentials = google.oauth2.credentials.Credentials(
59+
token=credentials.access_token,
60+
refresh_token=credentials.refresh_token,
61+
token_uri=credentials.token_uri,
62+
client_id=credentials.client_id,
63+
client_secret=credentials.client_secret,
64+
scopes=credentials.scopes)
65+
66+
new_credentials._expires = credentials.token_expiry
67+
68+
return new_credentials
69+
70+
71+
def _convert_service_account_credentials(credentials):
72+
"""Converts to :class:`google.oauth2.service_account.Credentials`.
73+
74+
Args:
75+
credentials (Union[
76+
oauth2client.service_account.ServiceAccountCredentials,
77+
oauth2client.service_account._JWTAccessCredentials]): The
78+
credentials to convert.
79+
80+
Returns:
81+
google.oauth2.service_account.Credentials: The converted credentials.
82+
"""
83+
info = credentials.serialization_data.copy()
84+
info['token_uri'] = credentials.token_uri
85+
return google.oauth2.service_account.Credentials.from_service_account_info(
86+
info)
87+
88+
89+
def _convert_gce_app_assertion_credentials(credentials):
90+
"""Converts to :class:`google.auth.compute_engine.Credentials`.
91+
92+
Args:
93+
credentials (oauth2client.contrib.gce.AppAssertionCredentials): The
94+
credentials to convert.
95+
96+
Returns:
97+
google.oauth2.service_account.Credentials: The converted credentials.
98+
"""
99+
return google.auth.compute_engine.Credentials(
100+
service_account_email=credentials.service_account_email)
101+
102+
103+
def _convert_appengine_app_assertion_credentials(credentials):
104+
"""Converts to :class:`google.auth.app_engine.Credentials`.
105+
106+
Args:
107+
credentials (oauth2client.contrib.app_engine.AppAssertionCredentials):
108+
The credentials to convert.
109+
110+
Returns:
111+
google.oauth2.service_account.Credentials: The converted credentials.
112+
"""
113+
# pylint: disable=invalid-name
114+
return google.auth.app_engine.Credentials(
115+
scopes=_helpers.string_to_scopes(credentials.scope),
116+
service_account_id=credentials.service_account_id)
117+
118+
119+
_CLASS_CONVERSION_MAP = {
120+
oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials,
121+
oauth2client.client.GoogleCredentials: _convert_oauth2_credentials,
122+
oauth2client.service_account.ServiceAccountCredentials:
123+
_convert_service_account_credentials,
124+
oauth2client.service_account._JWTAccessCredentials:
125+
_convert_service_account_credentials,
126+
oauth2client.contrib.gce.AppAssertionCredentials:
127+
_convert_gce_app_assertion_credentials,
128+
}
129+
130+
if _HAS_APPENGINE:
131+
_CLASS_CONVERSION_MAP[
132+
oauth2client.contrib.appengine.AppAssertionCredentials] = (
133+
_convert_appengine_app_assertion_credentials)
134+
135+
136+
def convert(credentials):
137+
"""Convert oauth2client credentials to google-auth credentials.
138+
139+
This class converts:
140+
141+
- :class:`oauth2client.client.OAuth2Credentials` to
142+
:class:`google.oauth2.credentials.Credentials`.
143+
- :class:`oauth2client.client.GoogleCredentials` to
144+
:class:`google.oauth2.credentials.Credentials`.
145+
- :class:`oauth2client.service_account.ServiceAccountCredentials` to
146+
:class:`google.oauth2.service_account.Credentials`.
147+
- :class:`oauth2client.service_account._JWTAccessCredentials` to
148+
:class:`google.oauth2.service_account.Credentials`.
149+
- :class:`oauth2client.contrib.gce.AppAssertionCredentials` to
150+
:class:`google.auth.compute_engine.Credentials`.
151+
- :class:`oauth2client.contrib.appengine.AppAssertionCredentials` to
152+
:class:`google.auth.app_engine.Credentials`.
153+
154+
Returns:
155+
google.auth.credentials.Credentials: The converted credentials.
156+
157+
Raises:
158+
ValueError: If the credentials could not be converted.
159+
"""
160+
161+
credentials_class = type(credentials)
162+
163+
try:
164+
return _CLASS_CONVERSION_MAP[credentials_class](credentials)
165+
except KeyError:
166+
raise ValueError(_CONVERT_ERROR_TMPL.format(credentials_class))

tests/conftest.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2016 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import sys
16+
17+
import mock
18+
import pytest
19+
20+
21+
@pytest.fixture
22+
def mock_non_existent_module(monkeypatch):
23+
"""Mocks a non-existing module in sys.modules.
24+
25+
Additionally mocks any non-existing modules specified in the dotted path.
26+
"""
27+
def _mock_non_existent_module(path):
28+
parts = path.split('.')
29+
partial = []
30+
for part in parts:
31+
partial.append(part)
32+
current_module = '.'.join(partial)
33+
if current_module not in sys.modules:
34+
monkeypatch.setitem(
35+
sys.modules, current_module, mock.MagicMock())
36+
37+
return _mock_non_existent_module

tests/test__oauth2client.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Copyright 2016 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import datetime
16+
import os
17+
import sys
18+
19+
import mock
20+
import oauth2client.client
21+
import oauth2client.contrib.gce
22+
import oauth2client.service_account
23+
import pytest
24+
from six.moves import reload_module
25+
26+
from google.auth import _oauth2client
27+
28+
29+
DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
30+
SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json')
31+
32+
33+
def test__convert_oauth2_credentials():
34+
old_credentials = oauth2client.client.OAuth2Credentials(
35+
'access_token', 'client_id', 'client_secret', 'refresh_token',
36+
datetime.datetime.min, 'token_uri', 'user_agent', scopes='one two')
37+
38+
new_credentials = _oauth2client._convert_oauth2_credentials(
39+
old_credentials)
40+
41+
assert new_credentials.token == old_credentials.access_token
42+
assert new_credentials._refresh_token == old_credentials.refresh_token
43+
assert new_credentials._client_id == old_credentials.client_id
44+
assert new_credentials._client_secret == old_credentials.client_secret
45+
assert new_credentials._token_uri == old_credentials.token_uri
46+
assert new_credentials.scopes == old_credentials.scopes
47+
48+
49+
def test__convert_service_account_credentials():
50+
old_class = oauth2client.service_account.ServiceAccountCredentials
51+
old_credentials = old_class.from_json_keyfile_name(
52+
SERVICE_ACCOUNT_JSON_FILE)
53+
54+
new_credentials = _oauth2client._convert_service_account_credentials(
55+
old_credentials)
56+
57+
assert (new_credentials._service_account_email ==
58+
old_credentials.service_account_email)
59+
assert new_credentials._signer.key_id == old_credentials._private_key_id
60+
assert new_credentials._token_uri == old_credentials.token_uri
61+
62+
63+
def test__convert_service_account_credentials_with_jwt():
64+
old_class = oauth2client.service_account._JWTAccessCredentials
65+
old_credentials = old_class.from_json_keyfile_name(
66+
SERVICE_ACCOUNT_JSON_FILE)
67+
68+
new_credentials = _oauth2client._convert_service_account_credentials(
69+
old_credentials)
70+
71+
assert (new_credentials._service_account_email ==
72+
old_credentials.service_account_email)
73+
assert new_credentials._signer.key_id == old_credentials._private_key_id
74+
assert new_credentials._token_uri == old_credentials.token_uri
75+
76+
77+
def test__convert_gce_app_assertion_credentials():
78+
old_credentials = oauth2client.contrib.gce.AppAssertionCredentials(
79+
email='some_email')
80+
81+
new_credentials = _oauth2client._convert_gce_app_assertion_credentials(
82+
old_credentials)
83+
84+
assert (new_credentials._service_account_email ==
85+
old_credentials.service_account_email)
86+
87+
88+
@pytest.fixture
89+
def mock_oauth2client_gae_imports(mock_non_existent_module):
90+
mock_non_existent_module('google.appengine.api.app_identity')
91+
mock_non_existent_module('google.appengine.ext.ndb')
92+
mock_non_existent_module('google.appengine.ext.webapp.util')
93+
mock_non_existent_module('webapp2')
94+
95+
96+
@mock.patch('google.auth.app_engine.app_identity')
97+
def test__convert_appengine_app_assertion_credentials(
98+
app_identity, mock_oauth2client_gae_imports):
99+
100+
import oauth2client.contrib.appengine
101+
102+
service_account_id = 'service_account_id'
103+
old_credentials = oauth2client.contrib.appengine.AppAssertionCredentials(
104+
scope='one two', service_account_id=service_account_id)
105+
106+
new_credentials = (
107+
_oauth2client._convert_appengine_app_assertion_credentials(
108+
old_credentials))
109+
110+
assert new_credentials.scopes == ['one', 'two']
111+
assert (new_credentials._service_account_id ==
112+
old_credentials.service_account_id)
113+
114+
115+
class MockCredentials(object):
116+
pass
117+
118+
119+
def test_convert_success():
120+
convert_function = mock.Mock()
121+
conversion_map_patch = mock.patch.object(
122+
_oauth2client, '_CLASS_CONVERSION_MAP',
123+
{MockCredentials: convert_function})
124+
credentials = MockCredentials()
125+
126+
with conversion_map_patch:
127+
result = _oauth2client.convert(credentials)
128+
129+
convert_function.assert_called_once_with(credentials)
130+
assert result == convert_function.return_value
131+
132+
133+
def test_convert_not_found():
134+
with pytest.raises(ValueError) as excinfo:
135+
_oauth2client.convert('a string is not a real credentials class')
136+
137+
assert excinfo.match('Unable to convert')
138+
139+
140+
@pytest.fixture
141+
def reset__oauth2client_module():
142+
"""Reloads the _oauth2client module after a test."""
143+
reload_module(_oauth2client)
144+
145+
146+
def test_import_has_app_engine(
147+
mock_oauth2client_gae_imports, reset__oauth2client_module):
148+
reload_module(_oauth2client)
149+
assert _oauth2client._HAS_APPENGINE
150+
151+
152+
def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module):
153+
monkeypatch.setitem(sys.modules, 'oauth2client', None)
154+
with pytest.raises(ImportError) as excinfo:
155+
reload_module(_oauth2client)
156+
157+
assert excinfo.match('oauth2client')

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ deps =
1111
urllib3
1212
certifi
1313
requests
14+
oauth2client
1415
grpcio; platform_python_implementation != 'PyPy'
1516
commands =
1617
py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests}

0 commit comments

Comments
 (0)