blob: 1cfcc7c6c1aa458c8d53f06f909dc55db7a2c8d3 [file] [log] [blame]
salrashid1231fbc6792018-11-09 11:05:34 -08001# Copyright 2018 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
15import datetime
16import json
17import os
18
19import mock
20import pytest
21from six.moves import http_client
22
23from google.auth import _helpers
24from google.auth import crypt
25from google.auth import exceptions
26from google.auth import impersonated_credentials
27from google.auth import transport
28from google.auth.impersonated_credentials import Credentials
29from google.oauth2 import service_account
30
Bu Sun Kim9eec0912019-10-21 17:04:21 -070031DATA_DIR = os.path.join(os.path.dirname(__file__), "", "data")
salrashid1231fbc6792018-11-09 11:05:34 -080032
Bu Sun Kim9eec0912019-10-21 17:04:21 -070033with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh:
salrashid1231fbc6792018-11-09 11:05:34 -080034 PRIVATE_KEY_BYTES = fh.read()
35
Bu Sun Kim9eec0912019-10-21 17:04:21 -070036SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json")
salrashid1231fbc6792018-11-09 11:05:34 -080037
Bu Sun Kim9eec0912019-10-21 17:04:21 -070038ID_TOKEN_DATA = (
39 "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNhZDk3N2Ew"
40 "Yjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwc"
41 "zovL2Zvby5iYXIiLCJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJle"
42 "HAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwiaXNzIjoiaHR0cHM6L"
43 "y9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwN"
44 "zA4NTY4In0.redacted"
45)
salrashid1237a8641a2019-08-07 14:31:33 -070046ID_TOKEN_EXPIRY = 1564475051
47
Bu Sun Kim9eec0912019-10-21 17:04:21 -070048with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh:
salrashid1231fbc6792018-11-09 11:05:34 -080049 SERVICE_ACCOUNT_INFO = json.load(fh)
50
Bu Sun Kim9eec0912019-10-21 17:04:21 -070051SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1")
52TOKEN_URI = "https://example.com/oauth2/token"
salrashid1231fbc6792018-11-09 11:05:34 -080053
54
55@pytest.fixture
56def mock_donor_credentials():
Bu Sun Kim9eec0912019-10-21 17:04:21 -070057 with mock.patch("google.oauth2._client.jwt_grant", autospec=True) as grant:
salrashid1231fbc6792018-11-09 11:05:34 -080058 grant.return_value = (
59 "source token",
60 _helpers.utcnow() + datetime.timedelta(seconds=500),
Bu Sun Kim9eec0912019-10-21 17:04:21 -070061 {},
62 )
salrashid1231fbc6792018-11-09 11:05:34 -080063 yield grant
64
65
salrashid1237a8641a2019-08-07 14:31:33 -070066class MockResponse:
67 def __init__(self, json_data, status_code):
68 self.json_data = json_data
69 self.status_code = status_code
70
71 def json(self):
72 return self.json_data
73
74
75@pytest.fixture
76def mock_authorizedsession_sign():
Bu Sun Kim9eec0912019-10-21 17:04:21 -070077 with mock.patch(
78 "google.auth.transport.requests.AuthorizedSession.request", autospec=True
79 ) as auth_session:
80 data = {"keyId": "1", "signedBlob": "c2lnbmF0dXJl"}
salrashid1237a8641a2019-08-07 14:31:33 -070081 auth_session.return_value = MockResponse(data, http_client.OK)
82 yield auth_session
83
84
85@pytest.fixture
86def mock_authorizedsession_idtoken():
Bu Sun Kim9eec0912019-10-21 17:04:21 -070087 with mock.patch(
88 "google.auth.transport.requests.AuthorizedSession.request", autospec=True
89 ) as auth_session:
90 data = {"token": ID_TOKEN_DATA}
salrashid1237a8641a2019-08-07 14:31:33 -070091 auth_session.return_value = MockResponse(data, http_client.OK)
92 yield auth_session
93
94
salrashid1231fbc6792018-11-09 11:05:34 -080095class TestImpersonatedCredentials(object):
96
Bu Sun Kim9eec0912019-10-21 17:04:21 -070097 SERVICE_ACCOUNT_EMAIL = "service-account@example.com"
98 TARGET_PRINCIPAL = "impersonated@project.iam.gserviceaccount.com"
99 TARGET_SCOPES = ["https://www.googleapis.com/auth/devstorage.read_only"]
salrashid1231fbc6792018-11-09 11:05:34 -0800100 DELEGATES = []
101 LIFETIME = 3600
102 SOURCE_CREDENTIALS = service_account.Credentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700103 SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI
104 )
salrashid1231fbc6792018-11-09 11:05:34 -0800105
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700106 def make_credentials(self, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL):
salrashid1237a8641a2019-08-07 14:31:33 -0700107
salrashid1231fbc6792018-11-09 11:05:34 -0800108 return Credentials(
109 source_credentials=self.SOURCE_CREDENTIALS,
salrashid1237a8641a2019-08-07 14:31:33 -0700110 target_principal=target_principal,
salrashid1231fbc6792018-11-09 11:05:34 -0800111 target_scopes=self.TARGET_SCOPES,
112 delegates=self.DELEGATES,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700113 lifetime=lifetime,
114 )
salrashid1231fbc6792018-11-09 11:05:34 -0800115
116 def test_default_state(self):
117 credentials = self.make_credentials()
118 assert not credentials.valid
119 assert credentials.expired
120
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700121 def make_request(self, data, status=http_client.OK, headers=None, side_effect=None):
salrashid1231fbc6792018-11-09 11:05:34 -0800122 response = mock.create_autospec(transport.Response, instance=False)
123 response.status = status
124 response.data = _helpers.to_bytes(data)
125 response.headers = headers or {}
126
127 request = mock.create_autospec(transport.Request, instance=False)
128 request.side_effect = side_effect
129 request.return_value = response
130
131 return request
132
133 def test_refresh_success(self, mock_donor_credentials):
134 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700135 token = "token"
salrashid1231fbc6792018-11-09 11:05:34 -0800136
137 expire_time = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700138 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500)
139 ).isoformat("T") + "Z"
140 response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1231fbc6792018-11-09 11:05:34 -0800141
142 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700143 data=json.dumps(response_body), status=http_client.OK
144 )
salrashid1231fbc6792018-11-09 11:05:34 -0800145
146 credentials.refresh(request)
147
148 assert credentials.valid
149 assert not credentials.expired
150
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700151 def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials):
salrashid1231fbc6792018-11-09 11:05:34 -0800152 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700153 token = "token"
salrashid1231fbc6792018-11-09 11:05:34 -0800154
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700155 expire_time = (_helpers.utcnow() + datetime.timedelta(seconds=500)).isoformat(
156 "T"
157 )
158 response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1231fbc6792018-11-09 11:05:34 -0800159
160 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700161 data=json.dumps(response_body), status=http_client.OK
162 )
salrashid1231fbc6792018-11-09 11:05:34 -0800163
164 with pytest.raises(exceptions.RefreshError) as excinfo:
165 credentials.refresh(request)
166
167 assert excinfo.match(impersonated_credentials._REFRESH_ERROR)
168
169 assert not credentials.valid
170 assert credentials.expired
171
salrashid1231fbc6792018-11-09 11:05:34 -0800172 def test_refresh_failure_unauthorzed(self, mock_donor_credentials):
173 credentials = self.make_credentials(lifetime=None)
174
175 response_body = {
176 "error": {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700177 "code": 403,
178 "message": "The caller does not have permission",
179 "status": "PERMISSION_DENIED",
salrashid1231fbc6792018-11-09 11:05:34 -0800180 }
181 }
182
183 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700184 data=json.dumps(response_body), status=http_client.UNAUTHORIZED
185 )
salrashid1231fbc6792018-11-09 11:05:34 -0800186
187 with pytest.raises(exceptions.RefreshError) as excinfo:
188 credentials.refresh(request)
189
190 assert excinfo.match(impersonated_credentials._REFRESH_ERROR)
191
192 assert not credentials.valid
193 assert credentials.expired
194
195 def test_refresh_failure_http_error(self, mock_donor_credentials):
196 credentials = self.make_credentials(lifetime=None)
197
198 response_body = {}
199
200 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700201 data=json.dumps(response_body), status=http_client.HTTPException
202 )
salrashid1231fbc6792018-11-09 11:05:34 -0800203
204 with pytest.raises(exceptions.RefreshError) as excinfo:
205 credentials.refresh(request)
206
207 assert excinfo.match(impersonated_credentials._REFRESH_ERROR)
208
209 assert not credentials.valid
210 assert credentials.expired
211
212 def test_expired(self):
213 credentials = self.make_credentials(lifetime=None)
214 assert credentials.expired
salrashid1237a8641a2019-08-07 14:31:33 -0700215
216 def test_signer(self):
217 credentials = self.make_credentials()
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700218 assert isinstance(credentials.signer, impersonated_credentials.Credentials)
salrashid1237a8641a2019-08-07 14:31:33 -0700219
220 def test_signer_email(self):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700221 credentials = self.make_credentials(target_principal=self.TARGET_PRINCIPAL)
salrashid1237a8641a2019-08-07 14:31:33 -0700222 assert credentials.signer_email == self.TARGET_PRINCIPAL
223
224 def test_service_account_email(self):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700225 credentials = self.make_credentials(target_principal=self.TARGET_PRINCIPAL)
salrashid1237a8641a2019-08-07 14:31:33 -0700226 assert credentials.service_account_email == self.TARGET_PRINCIPAL
227
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700228 def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign):
salrashid1237a8641a2019-08-07 14:31:33 -0700229 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700230 token = "token"
salrashid1237a8641a2019-08-07 14:31:33 -0700231
232 expire_time = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700233 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500)
234 ).isoformat("T") + "Z"
235 token_response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1237a8641a2019-08-07 14:31:33 -0700236
237 response = mock.create_autospec(transport.Response, instance=False)
238 response.status = http_client.OK
239 response.data = _helpers.to_bytes(json.dumps(token_response_body))
240
241 request = mock.create_autospec(transport.Request, instance=False)
242 request.return_value = response
243
244 credentials.refresh(request)
245
246 assert credentials.valid
247 assert not credentials.expired
248
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700249 signature = credentials.sign_bytes(b"signed bytes")
250 assert signature == b"signature"
salrashid1237a8641a2019-08-07 14:31:33 -0700251
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700252 def test_id_token_success(
253 self, mock_donor_credentials, mock_authorizedsession_idtoken
254 ):
salrashid1237a8641a2019-08-07 14:31:33 -0700255 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700256 token = "token"
257 target_audience = "https://foo.bar"
salrashid1237a8641a2019-08-07 14:31:33 -0700258
259 expire_time = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700260 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500)
261 ).isoformat("T") + "Z"
262 response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1237a8641a2019-08-07 14:31:33 -0700263
264 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700265 data=json.dumps(response_body), status=http_client.OK
266 )
salrashid1237a8641a2019-08-07 14:31:33 -0700267
268 credentials.refresh(request)
269
270 assert credentials.valid
271 assert not credentials.expired
272
273 id_creds = impersonated_credentials.IDTokenCredentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700274 credentials, target_audience=target_audience
275 )
salrashid1237a8641a2019-08-07 14:31:33 -0700276 id_creds.refresh(request)
277
278 assert id_creds.token == ID_TOKEN_DATA
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700279 assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY)
salrashid1237a8641a2019-08-07 14:31:33 -0700280
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700281 def test_id_token_from_credential(
282 self, mock_donor_credentials, mock_authorizedsession_idtoken
283 ):
salrashid1237a8641a2019-08-07 14:31:33 -0700284 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700285 token = "token"
286 target_audience = "https://foo.bar"
salrashid1237a8641a2019-08-07 14:31:33 -0700287
288 expire_time = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700289 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500)
290 ).isoformat("T") + "Z"
291 response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1237a8641a2019-08-07 14:31:33 -0700292
293 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700294 data=json.dumps(response_body), status=http_client.OK
295 )
salrashid1237a8641a2019-08-07 14:31:33 -0700296
297 credentials.refresh(request)
298
299 assert credentials.valid
300 assert not credentials.expired
301
302 id_creds = impersonated_credentials.IDTokenCredentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700303 credentials, target_audience=target_audience
304 )
salrashid1237a8641a2019-08-07 14:31:33 -0700305 id_creds = id_creds.from_credentials(target_credentials=credentials)
306 id_creds.refresh(request)
307
308 assert id_creds.token == ID_TOKEN_DATA
309
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700310 def test_id_token_with_target_audience(
311 self, mock_donor_credentials, mock_authorizedsession_idtoken
312 ):
salrashid1237a8641a2019-08-07 14:31:33 -0700313 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700314 token = "token"
315 target_audience = "https://foo.bar"
salrashid1237a8641a2019-08-07 14:31:33 -0700316
317 expire_time = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700318 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500)
319 ).isoformat("T") + "Z"
320 response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1237a8641a2019-08-07 14:31:33 -0700321
322 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700323 data=json.dumps(response_body), status=http_client.OK
324 )
salrashid1237a8641a2019-08-07 14:31:33 -0700325
326 credentials.refresh(request)
327
328 assert credentials.valid
329 assert not credentials.expired
330
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700331 id_creds = impersonated_credentials.IDTokenCredentials(credentials)
332 id_creds = id_creds.with_target_audience(target_audience=target_audience)
salrashid1237a8641a2019-08-07 14:31:33 -0700333 id_creds.refresh(request)
334
335 assert id_creds.token == ID_TOKEN_DATA
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700336 assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY)
salrashid1237a8641a2019-08-07 14:31:33 -0700337
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700338 def test_id_token_invalid_cred(
339 self, mock_donor_credentials, mock_authorizedsession_idtoken
340 ):
salrashid1237a8641a2019-08-07 14:31:33 -0700341 credentials = None
342
343 with pytest.raises(exceptions.GoogleAuthError) as excinfo:
344 impersonated_credentials.IDTokenCredentials(credentials)
345
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700346 assert excinfo.match("Provided Credential must be" " impersonated_credentials")
salrashid1237a8641a2019-08-07 14:31:33 -0700347
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700348 def test_id_token_with_include_email(
349 self, mock_donor_credentials, mock_authorizedsession_idtoken
350 ):
salrashid1237a8641a2019-08-07 14:31:33 -0700351 credentials = self.make_credentials(lifetime=None)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700352 token = "token"
353 target_audience = "https://foo.bar"
salrashid1237a8641a2019-08-07 14:31:33 -0700354
355 expire_time = (
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700356 _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500)
357 ).isoformat("T") + "Z"
358 response_body = {"accessToken": token, "expireTime": expire_time}
salrashid1237a8641a2019-08-07 14:31:33 -0700359
360 request = self.make_request(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700361 data=json.dumps(response_body), status=http_client.OK
362 )
salrashid1237a8641a2019-08-07 14:31:33 -0700363
364 credentials.refresh(request)
365
366 assert credentials.valid
367 assert not credentials.expired
368
369 id_creds = impersonated_credentials.IDTokenCredentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700370 credentials, target_audience=target_audience
371 )
salrashid1237a8641a2019-08-07 14:31:33 -0700372 id_creds = id_creds.with_include_email(True)
373 id_creds.refresh(request)
374
375 assert id_creds.token == ID_TOKEN_DATA