[stable-2.7] Store Authorization header inside HttpApi connection plugin (#45598) (#45608)

* Store Authorization header inside HttpApi connection plugin (#45598)


(cherry picked from commit b7263eab1d)

* Add changelog entry
This commit is contained in:
Anton Nikulin 2018-10-09 20:12:21 +03:00 committed by Toshio Kuratomi
parent d9bd76dc8a
commit d2c72040b5
3 changed files with 19 additions and 26 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- Fix the issue with refreshing the token by storing Authorization header inside HttpApi connection plugin.

View file

@ -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')

View file

@ -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'
}