From d2c72040b5c107497985a3b5e9f7356b996cf08c Mon Sep 17 00:00:00 2001 From: Anton Nikulin Date: Tue, 9 Oct 2018 20:12:21 +0300 Subject: [PATCH] [stable-2.7] Store Authorization header inside HttpApi connection plugin (#45598) (#45608) * Store Authorization header inside HttpApi connection plugin (#45598) (cherry picked from commit b7263eab1da5ac016de83a28ed9cf5a754b85b6d) * Add changelog entry --- .../ftd-http-api-plugin-refresh-token.yaml | 2 ++ lib/ansible/plugins/httpapi/ftd.py | 20 ++++------------ test/units/plugins/httpapi/test_ftd.py | 23 ++++++++++--------- 3 files changed, 19 insertions(+), 26 deletions(-) create mode 100644 changelogs/fragments/ftd-http-api-plugin-refresh-token.yaml diff --git a/changelogs/fragments/ftd-http-api-plugin-refresh-token.yaml b/changelogs/fragments/ftd-http-api-plugin-refresh-token.yaml new file mode 100644 index 00000000000..66ee2c20e94 --- /dev/null +++ b/changelogs/fragments/ftd-http-api-plugin-refresh-token.yaml @@ -0,0 +1,2 @@ +bugfixes: +- Fix the issue with refreshing the token by storing Authorization header inside HttpApi connection plugin. diff --git a/lib/ansible/plugins/httpapi/ftd.py b/lib/ansible/plugins/httpapi/ftd.py index 2b1f45c5f2a..33366232ef9 100644 --- a/lib/ansible/plugins/httpapi/ftd.py +++ b/lib/ansible/plugins/httpapi/ftd.py @@ -109,6 +109,7 @@ class HttpApi(HttpApiBase): try: self.refresh_token = response['refresh_token'] self.access_token = response['access_token'] + self.connection._auth = {'Authorization': 'Bearer %s' % self.access_token} except KeyError: raise ConnectionError( 'Server returned response without token info during connection authentication: %s' % response) @@ -121,7 +122,7 @@ class HttpApi(HttpApiBase): } self.connection.send( self._get_api_token_path(), json.dumps(auth_payload), method=HTTPMethod.POST, - headers=self._authorized_headers() + headers=BASE_HEADERS ) self.refresh_token = None self.access_token = None @@ -134,10 +135,7 @@ class HttpApi(HttpApiBase): url = construct_url_path(url_path, path_params, query_params) data = json.dumps(body_params) if body_params else None try: - response, response_data = self.connection.send( - url, data, method=http_method, - headers=self._authorized_headers() - ) + response, response_data = self.connection.send(url, data, method=http_method, headers=BASE_HEADERS) return { ResponseParams.SUCCESS: True, ResponseParams.STATUS_CODE: response.getcode(), @@ -159,7 +157,7 @@ class HttpApi(HttpApiBase): rf.make_multipart() body, content_type = encode_multipart_formdata([rf]) - headers = self._authorized_headers() + headers = dict(BASE_HEADERS) headers['Content-Type'] = content_type headers['Content-Length'] = len(body) @@ -168,10 +166,7 @@ class HttpApi(HttpApiBase): def download_file(self, from_url, to_path, path_params=None): url = construct_url_path(from_url, path_params=path_params) - response, response_data = self.connection.send( - url, data=None, method=HTTPMethod.GET, - headers=self._authorized_headers() - ) + response, response_data = self.connection.send(url, data=None, method=HTTPMethod.GET, headers=BASE_HEADERS) if os.path.isdir(to_path): filename = extract_filename_from_headers(response.info()) @@ -188,11 +183,6 @@ class HttpApi(HttpApiBase): # None means that the exception will be passed further to the caller return None - def _authorized_headers(self): - headers = dict(BASE_HEADERS) - headers['Authorization'] = 'Bearer %s' % self.access_token - return headers - def _get_api_spec_path(self): return self.get_option('spec_path') diff --git a/test/units/plugins/httpapi/test_ftd.py b/test/units/plugins/httpapi/test_ftd.py index 4832b47b8f5..cf54eec2da3 100644 --- a/test/units/plugins/httpapi/test_ftd.py +++ b/test/units/plugins/httpapi/test_ftd.py @@ -36,6 +36,11 @@ if PY3: else: BUILTINS_NAME = '__builtin__' +EXPECTED_BASE_HEADERS = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' +} + class FakeFtdHttpApiPlugin(HttpApi): def __init__(self, conn): @@ -66,6 +71,7 @@ class TestFtdHttpApi(unittest.TestCase): assert 'ACCESS_TOKEN' == self.ftd_plugin.access_token assert 'REFRESH_TOKEN' == self.ftd_plugin.refresh_token + assert {'Authorization': 'Bearer ACCESS_TOKEN'} == self.ftd_plugin.connection._auth expected_body = json.dumps({'grant_type': 'password', 'username': 'foo', 'password': 'bar'}) self.connection_mock.send.assert_called_once_with(mock.ANY, expected_body, headers=mock.ANY, method=mock.ANY) @@ -79,6 +85,7 @@ class TestFtdHttpApi(unittest.TestCase): assert 'NEW_ACCESS_TOKEN' == self.ftd_plugin.access_token assert 'NEW_REFRESH_TOKEN' == self.ftd_plugin.refresh_token + assert {'Authorization': 'Bearer NEW_ACCESS_TOKEN'} == self.ftd_plugin.connection._auth expected_body = json.dumps({'grant_type': 'refresh_token', 'refresh_token': 'REFRESH_TOKEN'}) self.connection_mock.send.assert_called_once_with(mock.ANY, expected_body, headers=mock.ANY, method=mock.ANY) @@ -91,7 +98,8 @@ class TestFtdHttpApi(unittest.TestCase): self.ftd_plugin.login('foo', 'bar') - self.connection_mock.send.assert_called_once_with('/testFakeLoginUrl', mock.ANY, headers=mock.ANY, method=mock.ANY) + self.connection_mock.send.assert_called_once_with('/testFakeLoginUrl', mock.ANY, headers=mock.ANY, + method=mock.ANY) self.ftd_plugin.hostvars['token_path'] = temp_token_path def test_login_raises_exception_when_no_refresh_token_and_no_credentials(self): @@ -134,7 +142,7 @@ class TestFtdHttpApi(unittest.TestCase): assert {ResponseParams.SUCCESS: True, ResponseParams.STATUS_CODE: 200, ResponseParams.RESPONSE: exp_resp} == resp self.connection_mock.send.assert_called_once_with('/test/123?at=0', '{"name": "foo"}', method=HTTPMethod.PUT, - headers=self._expected_headers()) + headers=EXPECTED_BASE_HEADERS) def test_send_request_should_return_empty_dict_when_no_response_data(self): self.connection_mock.send.return_value = self._connection_response(None) @@ -143,7 +151,7 @@ class TestFtdHttpApi(unittest.TestCase): assert {ResponseParams.SUCCESS: True, ResponseParams.STATUS_CODE: 200, ResponseParams.RESPONSE: {}} == resp self.connection_mock.send.assert_called_once_with('/test', None, method=HTTPMethod.GET, - headers=self._expected_headers()) + headers=EXPECTED_BASE_HEADERS) def test_send_request_should_return_error_info_when_http_error_raises(self): self.connection_mock.send.side_effect = HTTPError('http://testhost.com', 500, '', {}, @@ -214,7 +222,7 @@ class TestFtdHttpApi(unittest.TestCase): resp = self.ftd_plugin.upload_file('/tmp/test.txt', '/files') assert {'id': '123'} == resp - exp_headers = self._expected_headers() + exp_headers = dict(EXPECTED_BASE_HEADERS) exp_headers['Content-Length'] = len('--Encoded data--') exp_headers['Content-Type'] = 'multipart/form-data' self.connection_mock.send.assert_called_once_with('/files', data='--Encoded data--', @@ -261,10 +269,3 @@ class TestFtdHttpApi(unittest.TestCase): response_text = json.dumps(response) if type(response) is dict else response response_data = BytesIO(response_text.encode() if response_text else ''.encode()) return response_mock, response_data - - def _expected_headers(self): - return { - 'Accept': 'application/json', - 'Authorization': 'Bearer %s' % self.ftd_plugin.access_token, - 'Content-Type': 'application/json' - }