From 240c4129a7d9d3849c2a9a0566d79cddfc7b89de Mon Sep 17 00:00:00 2001 From: Liran Nuna Date: Thu, 6 Oct 2016 11:50:22 -0700 Subject: [PATCH] Try to revoke token with POST when getting a 405 The OAuth spec does not specify the HTTP verb explicitly but it does hint that POST is the correct verb. When using the client library with other OAuth services that implement revocation token via a POST, revoking the token will fail. This commit adds the ability to re-try the revocation process if we get a 405 with the POST verb. --- oauth2client/client.py | 4 +++ tests/test_client.py | 58 ++++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 704c61034..0a1aff9a0 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -836,6 +836,10 @@ def _do_revoke(self, http, token): token_revoke_uri = _helpers.update_query_params( self.revoke_uri, query_params) resp, content = transport.request(http, token_revoke_uri) + if resp.status == http_client.METHOD_NOT_ALLOWED: + body = urllib.parse.urlencode(query_params) + resp, content = transport.request(http, token_revoke_uri, + method='POST', body=body) if resp.status == http_client.OK: self.invalid = True else: diff --git a/tests/test_client.py b/tests/test_client.py index a3268ba84..786b818ad 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -819,8 +819,8 @@ def locked_delete(self): self.delete_called = True -def _token_revoke_test_helper(testcase, status, revoke_raise, - valid_bool_value, token_attr): +def _token_revoke_test_helper(testcase, revoke_raise, valid_bool_value, + token_attr, http_mock): current_store = getattr(testcase.credentials, 'store', None) dummy_store = DummyDeleteStorage() @@ -834,12 +834,11 @@ def do_revoke_stub(http, token): return actual_do_revoke(http, token) testcase.credentials._do_revoke = do_revoke_stub - http = http_mock.HttpMock(headers={'status': status}) if revoke_raise: testcase.assertRaises(client.TokenRevokeError, - testcase.credentials.revoke, http) + testcase.credentials.revoke, http_mock) else: - testcase.credentials.revoke(http) + testcase.credentials.revoke(http_mock) testcase.assertEqual(getattr(testcase.credentials, token_attr), testcase.token_from_revoke) @@ -922,21 +921,38 @@ def test_token_refresh_failure(self): self.assertEqual(None, self.credentials.token_response) def test_token_revoke_success(self): + http = http_mock.HttpMock(headers={'status': http_client.OK}) _token_revoke_test_helper( - self, '200', revoke_raise=False, - valid_bool_value=True, token_attr='refresh_token') + self, revoke_raise=False, valid_bool_value=True, + token_attr='refresh_token', http_mock=http) def test_token_revoke_failure(self): + http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST}) _token_revoke_test_helper( - self, '400', revoke_raise=True, - valid_bool_value=False, token_attr='refresh_token') + self, revoke_raise=True, valid_bool_value=False, + token_attr='refresh_token', http_mock=http) def test_token_revoke_fallback(self): original_credentials = self.credentials.to_json() self.credentials.refresh_token = None + + http = http_mock.HttpMock(headers={'status': http_client.OK}) + _token_revoke_test_helper( + self, revoke_raise=False, valid_bool_value=True, + token_attr='access_token', http_mock=http) + self.credentials = self.credentials.from_json(original_credentials) + + def test_token_revoke_405(self): + original_credentials = self.credentials.to_json() + self.credentials.refresh_token = None + + http = http_mock.HttpMockSequence([ + ({'status': http_client.METHOD_NOT_ALLOWED}, b''), + ({'status': http_client.OK}, b''), + ]) _token_revoke_test_helper( - self, '200', revoke_raise=False, - valid_bool_value=True, token_attr='access_token') + self, revoke_raise=False, valid_bool_value=True, + token_attr='access_token', http_mock=http) self.credentials = self.credentials.from_json(original_credentials) def test_non_401_error_response(self): @@ -1483,14 +1499,16 @@ def test_token_refresh_success(self): resp, content = transport.request(http, 'http://example.com') def test_token_revoke_success(self): + http = http_mock.HttpMock(headers={'status': http_client.OK}) _token_revoke_test_helper( - self, '200', revoke_raise=False, - valid_bool_value=True, token_attr='access_token') + self, revoke_raise=False, valid_bool_value=True, + token_attr='access_token', http_mock=http) def test_token_revoke_failure(self): + http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST}) _token_revoke_test_helper( - self, '400', revoke_raise=True, - valid_bool_value=False, token_attr='access_token') + self, revoke_raise=True, valid_bool_value=False, + token_attr='access_token', http_mock=http) def test_non_401_error_response(self): http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST}) @@ -1543,14 +1561,16 @@ def test_assertion_refresh(self): self.assertEqual(b'Bearer 1/3w', content[b'Authorization']) def test_token_revoke_success(self): + http = http_mock.HttpMock(headers={'status': http_client.OK}) _token_revoke_test_helper( - self, '200', revoke_raise=False, - valid_bool_value=True, token_attr='access_token') + self, revoke_raise=False, valid_bool_value=True, + token_attr='access_token', http_mock=http) def test_token_revoke_failure(self): + http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST}) _token_revoke_test_helper( - self, '400', revoke_raise=True, - valid_bool_value=False, token_attr='access_token') + self, revoke_raise=True, valid_bool_value=False, + token_attr='access_token', http_mock=http) def test_sign_blob_abstract(self): credentials = client.AssertionCredentials(None)