def test_token_refresh_store_expires_soon(self): # Tests the case where an access token that is valid when it is read # from the store expires before the original request succeeds. expiration = (datetime.datetime.utcnow() + datetime.timedelta(minutes=15)) credentials = self._create_test_credentials(expiration=expiration) storage = file_module.Storage(FILENAME) storage.put(credentials) credentials = storage.get() new_cred = copy.copy(credentials) new_cred.access_token = 'bar' storage.put(new_cred) access_token = '1/3w' token_response = {'access_token': access_token, 'expires_in': 3600} http = http_mock.HttpMockSequence([ ({'status': http_client.UNAUTHORIZED}, b'Initial token expired'), ({'status': http_client.UNAUTHORIZED}, b'Store token expired'), ({'status': http_client.OK}, json.dumps(token_response).encode('utf-8')), ({'status': http_client.OK}, b'Valid response to original request') ]) credentials.authorize(http) transport.request(http, 'https://example.com') self.assertEqual(credentials.access_token, access_token)
def test_token_refresh_store_expires_soon(self): # Tests the case where an access token that is valid when it is read # from the store expires before the original request succeeds. expiration = (datetime.datetime.utcnow() + datetime.timedelta(minutes=15)) credentials = self._create_test_credentials(expiration=expiration) storage = file_module.Storage(FILENAME) storage.put(credentials) credentials = storage.get() new_cred = copy.copy(credentials) new_cred.access_token = 'bar' storage.put(new_cred) access_token = '1/3w' token_response = {'access_token': access_token, 'expires_in': 3600} http = http_mock.HttpMockSequence([ ({ 'status': http_client.UNAUTHORIZED }, b'Initial token expired'), ({ 'status': http_client.UNAUTHORIZED }, b'Store token expired'), ({ 'status': http_client.OK }, json.dumps(token_response).encode('utf-8')), ({ 'status': http_client.OK }, b'Valid response to original request') ]) credentials.authorize(http) transport.request(http, 'https://example.com') self.assertEqual(credentials.access_token, access_token)
def test_token_refresh_store_expires_soon(self): # Tests the case where an access token that is valid when it is read # from the store expires before the original request succeeds. expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15) credentials = self._create_test_credentials(expiration=expiration) storage = file_module.Storage(FILENAME) storage.put(credentials) credentials = storage.get() new_cred = copy.copy(credentials) new_cred.access_token = "bar" storage.put(new_cred) access_token = "1/3w" token_response = {"access_token": access_token, "expires_in": 3600} http = http_mock.HttpMockSequence( [ ({"status": http_client.UNAUTHORIZED}, b"Initial token expired"), ({"status": http_client.UNAUTHORIZED}, b"Store token expired"), ({"status": http_client.OK}, json.dumps(token_response).encode("utf-8")), ({"status": http_client.OK}, b"Valid response to original request"), ] ) credentials.authorize(http) transport.request(http, "https://example.com") self.assertEqual(credentials.access_token, access_token)
def test_authorize_success(self, time, utcnow): utcnow.return_value = T1_DATE time.return_value = T1 http = http_mock.HttpMockSequence([ ({'status': http_client.OK}, b''), ({'status': http_client.OK}, b''), ]) self.jwt.authorize(http) transport.request(http, self.url) # Ensure we use the cached token utcnow.return_value = T2_DATE transport.request(http, self.url) # Verify mocks. certs = {'key': datafile('public_cert.pem')} self.assertEqual(len(http.requests), 2) for info in http.requests: self.assertEqual(info['method'], 'GET') self.assertEqual(info['uri'], self.url) self.assertIsNone(info['body']) self.assertEqual(len(info['headers']), 1) bearer, token = info['headers'][b'Authorization'].split() self.assertEqual(bearer, b'Bearer') payload = crypt.verify_signed_jwt_with_certs( token, certs, audience=self.url) self.assertEqual(len(payload), 5) self.assertEqual(payload['iss'], self.service_account_email) self.assertEqual(payload['sub'], self.service_account_email) self.assertEqual(payload['iat'], T1) self.assertEqual(payload['exp'], T1_EXPIRY) self.assertEqual(payload['aud'], self.url)
def test_authorize_401(self, utcnow): utcnow.return_value = T1_DATE http = http_mock.HttpMockSequence([ ({ 'status': http_client.OK }, b''), ({ 'status': http_client.UNAUTHORIZED }, b''), ({ 'status': http_client.OK }, b''), ]) self.jwt.authorize(http) transport.request(http, self.url) token_1 = self.jwt.access_token utcnow.return_value = T2_DATE response, _ = transport.request(http, self.url) self.assertEquals(response.status, http_client.OK) token_2 = self.jwt.access_token # Check the 401 forced a new token self.assertNotEqual(token_1, token_2) # Verify mocks. certs = {'key': datafile('public_cert.pem')} self.assertEqual(len(http.requests), 3) issued_at_vals = (T1, T1, T2) exp_vals = (T1_EXPIRY, T1_EXPIRY, T2_EXPIRY) for info, issued_at, exp_val in zip(http.requests, issued_at_vals, exp_vals): self.assertEqual(info['uri'], self.url) self.assertEqual(info['method'], 'GET') self.assertIsNone(info['body']) self.assertEqual(len(info['headers']), 1) bearer, token = info['headers'][b'Authorization'].split() self.assertEqual(bearer, b'Bearer') # To parse the token, skip the time check, since this # test intentionally has stale tokens. with mock.patch('oauth2client.crypt._verify_time_range', return_value=True): payload = crypt.verify_signed_jwt_with_certs(token, certs, audience=self.url) self.assertEqual(len(payload), 5) self.assertEqual(payload['iss'], self.service_account_email) self.assertEqual(payload['sub'], self.service_account_email) self.assertEqual(payload['iat'], issued_at) self.assertEqual(payload['exp'], exp_val) self.assertEqual(payload['aud'], self.url)
def get(http, path, root=METADATA_ROOT, recursive=None): """Fetch a resource from the metadata server. Args: http: an object to be used to make HTTP requests. path: A string indicating the resource to retrieve. For example, 'instance/service-accounts/default' root: A string indicating the full path to the metadata server root. recursive: A boolean indicating whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents Returns: A dictionary if the metadata server returns JSON, otherwise a string. Raises: http_client.HTTPException if an error corrured while retrieving metadata. """ url = urlparse.urljoin(root, path) url = _helpers._add_query_parameter(url, 'recursive', recursive) response, content = transport.request( http, url, headers=METADATA_HEADERS) if response.status == http_client.OK: decoded = _helpers._from_bytes(content) if response['content-type'] == 'application/json': return json.loads(decoded) else: return decoded else: raise http_client.HTTPException( 'Failed to retrieve {0} from the Google Compute Engine' 'metadata service. Response:\n{1}'.format(url, response))
def test_token_refresh_stream_body(self): expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15) credentials = self._create_test_credentials(expiration=expiration) storage = file_module.Storage(FILENAME) storage.put(credentials) credentials = storage.get() new_cred = copy.copy(credentials) new_cred.access_token = "bar" storage.put(new_cred) valid_access_token = "1/3w" token_response = {"access_token": valid_access_token, "expires_in": 3600} http = http_mock.HttpMockSequence( [ ({"status": http_client.UNAUTHORIZED}, b"Initial token expired"), ({"status": http_client.UNAUTHORIZED}, b"Store token expired"), ({"status": http_client.OK}, json.dumps(token_response).encode("utf-8")), ({"status": http_client.OK}, "echo_request_body"), ] ) body = six.StringIO("streaming body") credentials.authorize(http) _, content = transport.request(http, "https://example.com", body=body) self.assertEqual(content, "streaming body") self.assertEqual(credentials.access_token, valid_access_token)
def test_token_refresh_stream_body(self): expiration = (datetime.datetime.utcnow() + datetime.timedelta(minutes=15)) credentials = self._create_test_credentials(expiration=expiration) storage = file_module.Storage(FILENAME) storage.put(credentials) credentials = storage.get() new_cred = copy.copy(credentials) new_cred.access_token = 'bar' storage.put(new_cred) valid_access_token = '1/3w' token_response = {'access_token': valid_access_token, 'expires_in': 3600} http = http_mock.HttpMockSequence([ ({'status': http_client.UNAUTHORIZED}, b'Initial token expired'), ({'status': http_client.UNAUTHORIZED}, b'Store token expired'), ({'status': http_client.OK}, json.dumps(token_response).encode('utf-8')), ({'status': http_client.OK}, 'echo_request_body') ]) body = six.StringIO('streaming body') credentials.authorize(http) _, content = transport.request(http, 'https://example.com', body=body) self.assertEqual(content, 'streaming body') self.assertEqual(credentials.access_token, valid_access_token)
def get(http, path, root=METADATA_ROOT, recursive=None): """Fetch a resource from the metadata server. Args: http: an object to be used to make HTTP requests. path: A string indicating the resource to retrieve. For example, 'instance/service-accounts/default' root: A string indicating the full path to the metadata server root. recursive: A boolean indicating whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents Returns: A dictionary if the metadata server returns JSON, otherwise a string. Raises: http_client.HTTPException if an error corrured while retrieving metadata. """ url = urlparse.urljoin(root, path) url = _helpers._add_query_parameter(url, 'recursive', recursive) response, content = transport.request(http, url, headers=METADATA_HEADERS) if response.status == http_client.OK: decoded = _helpers._from_bytes(content) if response['content-type'] == 'application/json': return json.loads(decoded) else: return decoded else: raise http_client.HTTPException( 'Failed to retrieve {0} from the Google Compute Engine' 'metadata service. Response:\n{1}'.format(url, response))
def http_request(uri, method, body, headers): response, content = transport.request(http, uri, method=method, body=body, headers=headers) content = _helpers._from_bytes(content) return response, content
def test_authorize_401(self, utcnow): utcnow.return_value = T1_DATE http = http_mock.HttpMockSequence([ ({'status': http_client.OK}, b''), ({'status': http_client.UNAUTHORIZED}, b''), ({'status': http_client.OK}, b''), ]) self.jwt.authorize(http) transport.request(http, self.url) token_1 = self.jwt.access_token utcnow.return_value = T2_DATE response, _ = transport.request(http, self.url) self.assertEquals(response.status, http_client.OK) token_2 = self.jwt.access_token # Check the 401 forced a new token self.assertNotEqual(token_1, token_2) # Verify mocks. certs = {'key': datafile('public_cert.pem')} self.assertEqual(len(http.requests), 3) issued_at_vals = (T1, T1, T2) exp_vals = (T1_EXPIRY, T1_EXPIRY, T2_EXPIRY) for info, issued_at, exp_val in zip(http.requests, issued_at_vals, exp_vals): self.assertEqual(info['uri'], self.url) self.assertEqual(info['method'], 'GET') self.assertIsNone(info['body']) self.assertEqual(len(info['headers']), 1) bearer, token = info['headers'][b'Authorization'].split() self.assertEqual(bearer, b'Bearer') # To parse the token, skip the time check, since this # test intentionally has stale tokens. with mock.patch('oauth2client.crypt._verify_time_range', return_value=True): payload = crypt.verify_signed_jwt_with_certs( token, certs, audience=self.url) self.assertEqual(len(payload), 5) self.assertEqual(payload['iss'], self.service_account_email) self.assertEqual(payload['sub'], self.service_account_email) self.assertEqual(payload['iat'], issued_at) self.assertEqual(payload['exp'], exp_val) self.assertEqual(payload['aud'], self.url)
def test_credentials_good(self): credentials = self._make_credentials() http = http_mock.HttpMockSequence([ ({'status': http_client.OK}, b'{"access_token":"1/3w","expires_in":3600}'), ({'status': http_client.OK}, 'echo_request_headers'), ]) http = credentials.authorize(http) resp, content = transport.request(http, 'http://example.org') self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
def _check_user_info(credentials, expected_email): http = credentials.authorize(transport.get_http_object()) response, content = transport.request(http, USER_INFO) if response.status != http_client.OK: raise ValueError('Expected 200 OK response.') content = content.decode('utf-8') payload = json.loads(content) if payload['email'] != expected_email: raise ValueError('User info email does not match credentials.')
def test_authorize_no_aud(self, time, utcnow): utcnow.return_value = T1_DATE time.return_value = T1 jwt = service_account._JWTAccessCredentials( self.service_account_email, self.signer, private_key_id=self.private_key_id, client_id=self.client_id) http = http_mock.HttpMockSequence([ ({ 'status': http_client.OK }, b''), ]) jwt.authorize(http) transport.request(http, self.url) # Ensure we do not cache the token self.assertIsNone(jwt.access_token) # Verify mocks. self.assertEqual(len(http.requests), 1) info = http.requests[0] self.assertEqual(info['method'], 'GET') self.assertEqual(info['uri'], self.url) self.assertIsNone(info['body']) self.assertEqual(len(info['headers']), 1) bearer, token = info['headers'][b'Authorization'].split() self.assertEqual(bearer, b'Bearer') certs = {'key': datafile('public_cert.pem')} payload = crypt.verify_signed_jwt_with_certs(token, certs, audience=self.url) self.assertEqual(len(payload), 5) self.assertEqual(payload['iss'], self.service_account_email) self.assertEqual(payload['sub'], self.service_account_email) self.assertEqual(payload['iat'], T1) self.assertEqual(payload['exp'], T1_EXPIRY) self.assertEqual(payload['aud'], self.url)
def _credentials_refresh(self, credentials): http = http_mock.HttpMockSequence([ ({'status': http_client.OK}, b'{"access_token":"1/3w","expires_in":3600}'), ({'status': http_client.UNAUTHORIZED}, b''), ({'status': http_client.OK}, b'{"access_token":"3/3w","expires_in":3600}'), ({'status': http_client.OK}, 'echo_request_headers'), ]) http = credentials.authorize(http) _, content = transport.request(http, 'http://example.org') return content
def test_authorize_success(self, time, utcnow): utcnow.return_value = T1_DATE time.return_value = T1 http = http_mock.HttpMockSequence([ ({ 'status': http_client.OK }, b''), ({ 'status': http_client.OK }, b''), ]) self.jwt.authorize(http) transport.request(http, self.url) # Ensure we use the cached token utcnow.return_value = T2_DATE transport.request(http, self.url) # Verify mocks. certs = {'key': datafile('public_cert.pem')} self.assertEqual(len(http.requests), 2) for info in http.requests: self.assertEqual(info['method'], 'GET') self.assertEqual(info['uri'], self.url) self.assertIsNone(info['body']) self.assertEqual(len(info['headers']), 1) bearer, token = info['headers'][b'Authorization'].split() self.assertEqual(bearer, b'Bearer') payload = crypt.verify_signed_jwt_with_certs(token, certs, audience=self.url) self.assertEqual(len(payload), 5) self.assertEqual(payload['iss'], self.service_account_email) self.assertEqual(payload['sub'], self.service_account_email) self.assertEqual(payload['iat'], T1) self.assertEqual(payload['exp'], T1_EXPIRY) self.assertEqual(payload['aud'], self.url)
def test_credentials_good(self): credentials = self._make_credentials() http = http_mock.HttpMockSequence([ ({ 'status': http_client.OK }, b'{"access_token":"1/3w","expires_in":3600}'), ({ 'status': http_client.OK }, 'echo_request_headers'), ]) http = credentials.authorize(http) resp, content = transport.request(http, 'http://example.org') self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
def test_with_callable_http(self): mock_result = object() http = mock.Mock(return_value=mock_result) del http.request # Make sure the mock doesn't have a request attr. result = transport.request(http, self.uri, method=self.method, body=self.body, redirections=self.redirections) self.assertIs(result, mock_result) # Verify mock. http.assert_called_once_with(self.uri, method=self.method, body=self.body, redirections=self.redirections, headers=None, connection_type=None)
def test_authorize_no_aud(self, time, utcnow): utcnow.return_value = T1_DATE time.return_value = T1 jwt = service_account._JWTAccessCredentials( self.service_account_email, self.signer, private_key_id=self.private_key_id, client_id=self.client_id) http = http_mock.HttpMockSequence([ ({'status': http_client.OK}, b''), ]) jwt.authorize(http) transport.request(http, self.url) # Ensure we do not cache the token self.assertIsNone(jwt.access_token) # Verify mocks. self.assertEqual(len(http.requests), 1) info = http.requests[0] self.assertEqual(info['method'], 'GET') self.assertEqual(info['uri'], self.url) self.assertIsNone(info['body']) self.assertEqual(len(info['headers']), 1) bearer, token = info['headers'][b'Authorization'].split() self.assertEqual(bearer, b'Bearer') certs = {'key': datafile('public_cert.pem')} payload = crypt.verify_signed_jwt_with_certs( token, certs, audience=self.url) self.assertEqual(len(payload), 5) self.assertEqual(payload['iss'], self.service_account_email) self.assertEqual(payload['sub'], self.service_account_email) self.assertEqual(payload['iat'], T1) self.assertEqual(payload['exp'], T1_EXPIRY) self.assertEqual(payload['aud'], self.url)
def test_with_callable_http(self): headers = {} mock_result = object() http = http_mock.HttpMock(headers=headers, data=mock_result) result = transport.request(http, self.uri, method=self.method, body=self.body, redirections=self.redirections) self.assertEqual(result, (headers, mock_result)) # Verify mock. self.assertEqual(http.requests, 1) self.assertEqual(http.uri, self.uri) self.assertEqual(http.method, self.method) self.assertEqual(http.body, self.body) self.assertIsNone(http.headers) self.assertEqual(http.redirections, self.redirections)
def test_with_request_attr(self): mock_result = object() headers = {'foo': 'bar'} http = http_mock.HttpMock(headers=headers, data=mock_result) response, content = transport.request( http, self.uri, method=self.method, body=self.body, redirections=self.redirections) self.assertEqual(response, headers) self.assertIs(content, mock_result) # Verify mocks. self.assertEqual(http.requests, 1) self.assertEqual(http.uri, self.uri) self.assertEqual(http.method, self.method) self.assertEqual(http.body, self.body) self.assertIsNone(http.headers)
def test_with_request_attr(self): http = mock.Mock() mock_result = object() mock_request = mock.Mock(return_value=mock_result) http.request = mock_request result = transport.request(http, self.uri, method=self.method, body=self.body, redirections=self.redirections) self.assertIs(result, mock_result) # Verify mock. mock_request.assert_called_once_with(self.uri, method=self.method, body=self.body, redirections=self.redirections, headers=None, connection_type=None)
def _do_refresh_request(self, http, rapt_refreshed=False): """Refresh the access_token using the refresh_token. Args: http: An object to be used to make HTTP requests. rapt_refreshed: If we did or did not already refreshed the rapt token. Raises: HttpAccessTokenRefreshError: When the refresh fails. """ body = self._generate_refresh_request_body() headers = self._generate_refresh_request_headers() logger.info('Refreshing access_token') resp, content = transport.request(http, self.token_uri, method='POST', body=body, headers=headers) content = _helpers._from_bytes(content) if resp.status != http_client.OK: self._handle_refresh_error(http, rapt_refreshed, resp, content) return d = json.loads(content) self.token_response = d self.access_token = d['access_token'] self.refresh_token = d.get('refresh_token', self.refresh_token) if 'expires_in' in d: delta = datetime.timedelta(seconds=int(d['expires_in'])) self.token_expiry = delta + client._UTCNOW() else: self.token_expiry = None if 'id_token' in d: self.id_token = client._extract_id_token(d['id_token']) self.id_token_jwt = d['id_token'] else: self.id_token = None self.id_token_jwt = None # On temporary refresh errors, the user does not actually have to # re-authorize, so we unflag here. self.invalid = False if self.store: self.store.locked_put(self)
def test_with_request_attr(self): mock_result = object() headers = {'foo': 'bar'} http = http_mock.HttpMock(headers=headers, data=mock_result) response, content = transport.request(http, self.uri, method=self.method, body=self.body, redirections=self.redirections) self.assertEqual(response, headers) self.assertIs(content, mock_result) # Verify mocks. self.assertEqual(http.requests, 1) self.assertEqual(http.uri, self.uri) self.assertEqual(http.method, self.method) self.assertEqual(http.body, self.body) self.assertIsNone(http.headers)
def _credentials_refresh(self, credentials): http = http_mock.HttpMockSequence([ ({ 'status': http_client.OK }, b'{"access_token":"1/3w","expires_in":3600}'), ({ 'status': http_client.UNAUTHORIZED }, b''), ({ 'status': http_client.OK }, b'{"access_token":"3/3w","expires_in":3600}'), ({ 'status': http_client.OK }, 'echo_request_headers'), ]) http = credentials.authorize(http) _, content = transport.request(http, 'http://example.org') return content
def test_token_info(self): credentials = gce.AppAssertionCredentials([]) http = transport.get_http_object() # First refresh to get the access token. self.assertIsNone(credentials.access_token) credentials.refresh(http) self.assertIsNotNone(credentials.access_token) # Then check the access token against the token info API. query_params = {'access_token': credentials.access_token} token_uri = (oauth2client.GOOGLE_TOKEN_INFO_URI + '?' + urllib.parse.urlencode(query_params)) response, content = transport.request(http, token_uri) self.assertEqual(response.status, http_client.OK) content = content.decode('utf-8') payload = json.loads(content) self.assertEqual(payload['access_type'], 'offline') self.assertLessEqual(int(payload['expires_in']), 3600)
def test_token_refresh_stream_body(self): expiration = (datetime.datetime.utcnow() + datetime.timedelta(minutes=15)) credentials = self._create_test_credentials(expiration=expiration) storage = file_module.Storage(FILENAME) storage.put(credentials) credentials = storage.get() new_cred = copy.copy(credentials) new_cred.access_token = 'bar' storage.put(new_cred) valid_access_token = '1/3w' token_response = { 'access_token': valid_access_token, 'expires_in': 3600 } http = http_mock.HttpMockSequence([ ({ 'status': http_client.UNAUTHORIZED }, b'Initial token expired'), ({ 'status': http_client.UNAUTHORIZED }, b'Store token expired'), ({ 'status': http_client.OK }, json.dumps(token_response).encode('utf-8')), ({ 'status': http_client.OK }, 'echo_request_body') ]) body = six.StringIO('streaming body') credentials.authorize(http) _, content = transport.request(http, 'https://example.com', body=body) self.assertEqual(content, 'streaming body') self.assertEqual(credentials.access_token, valid_access_token)