1# Copyright 2020 Google LLC
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 json
16
17import pytest
18
19from google.auth import exceptions
20from google.oauth2 import utils
21
22
23CLIENT_ID = "username"
24CLIENT_SECRET = "password"
25# Base64 encoding of "username:password"
26BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ="
27# Base64 encoding of "username:"
28BASIC_AUTH_ENCODING_SECRETLESS = "dXNlcm5hbWU6"
29
30
31class AuthHandler(utils.OAuthClientAuthHandler):
32    def __init__(self, client_auth=None):
33        super(AuthHandler, self).__init__(client_auth)
34
35    def apply_client_authentication_options(
36        self, headers, request_body=None, bearer_token=None
37    ):
38        return super(AuthHandler, self).apply_client_authentication_options(
39            headers, request_body, bearer_token
40        )
41
42
43class TestClientAuthentication(object):
44    @classmethod
45    def make_client_auth(cls, client_secret=None):
46        return utils.ClientAuthentication(
47            utils.ClientAuthType.basic, CLIENT_ID, client_secret
48        )
49
50    def test_initialization_with_client_secret(self):
51        client_auth = self.make_client_auth(CLIENT_SECRET)
52
53        assert client_auth.client_auth_type == utils.ClientAuthType.basic
54        assert client_auth.client_id == CLIENT_ID
55        assert client_auth.client_secret == CLIENT_SECRET
56
57    def test_initialization_no_client_secret(self):
58        client_auth = self.make_client_auth()
59
60        assert client_auth.client_auth_type == utils.ClientAuthType.basic
61        assert client_auth.client_id == CLIENT_ID
62        assert client_auth.client_secret is None
63
64
65class TestOAuthClientAuthHandler(object):
66    CLIENT_AUTH_BASIC = utils.ClientAuthentication(
67        utils.ClientAuthType.basic, CLIENT_ID, CLIENT_SECRET
68    )
69    CLIENT_AUTH_BASIC_SECRETLESS = utils.ClientAuthentication(
70        utils.ClientAuthType.basic, CLIENT_ID
71    )
72    CLIENT_AUTH_REQUEST_BODY = utils.ClientAuthentication(
73        utils.ClientAuthType.request_body, CLIENT_ID, CLIENT_SECRET
74    )
75    CLIENT_AUTH_REQUEST_BODY_SECRETLESS = utils.ClientAuthentication(
76        utils.ClientAuthType.request_body, CLIENT_ID
77    )
78
79    @classmethod
80    def make_oauth_client_auth_handler(cls, client_auth=None):
81        return AuthHandler(client_auth)
82
83    def test_apply_client_authentication_options_none(self):
84        headers = {"Content-Type": "application/json"}
85        request_body = {"foo": "bar"}
86        auth_handler = self.make_oauth_client_auth_handler()
87
88        auth_handler.apply_client_authentication_options(headers, request_body)
89
90        assert headers == {"Content-Type": "application/json"}
91        assert request_body == {"foo": "bar"}
92
93    def test_apply_client_authentication_options_basic(self):
94        headers = {"Content-Type": "application/json"}
95        request_body = {"foo": "bar"}
96        auth_handler = self.make_oauth_client_auth_handler(self.CLIENT_AUTH_BASIC)
97
98        auth_handler.apply_client_authentication_options(headers, request_body)
99
100        assert headers == {
101            "Content-Type": "application/json",
102            "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING),
103        }
104        assert request_body == {"foo": "bar"}
105
106    def test_apply_client_authentication_options_basic_nosecret(self):
107        headers = {"Content-Type": "application/json"}
108        request_body = {"foo": "bar"}
109        auth_handler = self.make_oauth_client_auth_handler(
110            self.CLIENT_AUTH_BASIC_SECRETLESS
111        )
112
113        auth_handler.apply_client_authentication_options(headers, request_body)
114
115        assert headers == {
116            "Content-Type": "application/json",
117            "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING_SECRETLESS),
118        }
119        assert request_body == {"foo": "bar"}
120
121    def test_apply_client_authentication_options_request_body(self):
122        headers = {"Content-Type": "application/json"}
123        request_body = {"foo": "bar"}
124        auth_handler = self.make_oauth_client_auth_handler(
125            self.CLIENT_AUTH_REQUEST_BODY
126        )
127
128        auth_handler.apply_client_authentication_options(headers, request_body)
129
130        assert headers == {"Content-Type": "application/json"}
131        assert request_body == {
132            "foo": "bar",
133            "client_id": CLIENT_ID,
134            "client_secret": CLIENT_SECRET,
135        }
136
137    def test_apply_client_authentication_options_request_body_nosecret(self):
138        headers = {"Content-Type": "application/json"}
139        request_body = {"foo": "bar"}
140        auth_handler = self.make_oauth_client_auth_handler(
141            self.CLIENT_AUTH_REQUEST_BODY_SECRETLESS
142        )
143
144        auth_handler.apply_client_authentication_options(headers, request_body)
145
146        assert headers == {"Content-Type": "application/json"}
147        assert request_body == {
148            "foo": "bar",
149            "client_id": CLIENT_ID,
150            "client_secret": "",
151        }
152
153    def test_apply_client_authentication_options_request_body_no_body(self):
154        headers = {"Content-Type": "application/json"}
155        auth_handler = self.make_oauth_client_auth_handler(
156            self.CLIENT_AUTH_REQUEST_BODY
157        )
158
159        with pytest.raises(exceptions.OAuthError) as excinfo:
160            auth_handler.apply_client_authentication_options(headers)
161
162        assert excinfo.match(r"HTTP request does not support request-body")
163
164    def test_apply_client_authentication_options_bearer_token(self):
165        bearer_token = "ACCESS_TOKEN"
166        headers = {"Content-Type": "application/json"}
167        request_body = {"foo": "bar"}
168        auth_handler = self.make_oauth_client_auth_handler()
169
170        auth_handler.apply_client_authentication_options(
171            headers, request_body, bearer_token
172        )
173
174        assert headers == {
175            "Content-Type": "application/json",
176            "Authorization": "Bearer {}".format(bearer_token),
177        }
178        assert request_body == {"foo": "bar"}
179
180    def test_apply_client_authentication_options_bearer_and_basic(self):
181        bearer_token = "ACCESS_TOKEN"
182        headers = {"Content-Type": "application/json"}
183        request_body = {"foo": "bar"}
184        auth_handler = self.make_oauth_client_auth_handler(self.CLIENT_AUTH_BASIC)
185
186        auth_handler.apply_client_authentication_options(
187            headers, request_body, bearer_token
188        )
189
190        # Bearer token should have higher priority.
191        assert headers == {
192            "Content-Type": "application/json",
193            "Authorization": "Bearer {}".format(bearer_token),
194        }
195        assert request_body == {"foo": "bar"}
196
197    def test_apply_client_authentication_options_bearer_and_request_body(self):
198        bearer_token = "ACCESS_TOKEN"
199        headers = {"Content-Type": "application/json"}
200        request_body = {"foo": "bar"}
201        auth_handler = self.make_oauth_client_auth_handler(
202            self.CLIENT_AUTH_REQUEST_BODY
203        )
204
205        auth_handler.apply_client_authentication_options(
206            headers, request_body, bearer_token
207        )
208
209        # Bearer token should have higher priority.
210        assert headers == {
211            "Content-Type": "application/json",
212            "Authorization": "Bearer {}".format(bearer_token),
213        }
214        assert request_body == {"foo": "bar"}
215
216
217def test__handle_error_response_code_only():
218    error_resp = {"error": "unsupported_grant_type"}
219    response_data = json.dumps(error_resp)
220
221    with pytest.raises(exceptions.OAuthError) as excinfo:
222        utils.handle_error_response(response_data)
223
224    assert excinfo.match(r"Error code unsupported_grant_type")
225
226
227def test__handle_error_response_code_description():
228    error_resp = {
229        "error": "unsupported_grant_type",
230        "error_description": "The provided grant_type is unsupported",
231    }
232    response_data = json.dumps(error_resp)
233
234    with pytest.raises(exceptions.OAuthError) as excinfo:
235        utils.handle_error_response(response_data)
236
237    assert excinfo.match(
238        r"Error code unsupported_grant_type: The provided grant_type is unsupported"
239    )
240
241
242def test__handle_error_response_code_description_uri():
243    error_resp = {
244        "error": "unsupported_grant_type",
245        "error_description": "The provided grant_type is unsupported",
246        "error_uri": "https://tools.ietf.org/html/rfc6749",
247    }
248    response_data = json.dumps(error_resp)
249
250    with pytest.raises(exceptions.OAuthError) as excinfo:
251        utils.handle_error_response(response_data)
252
253    assert excinfo.match(
254        r"Error code unsupported_grant_type: The provided grant_type is unsupported - https://tools.ietf.org/html/rfc6749"
255    )
256
257
258def test__handle_error_response_non_json():
259    response_data = "Oops, something wrong happened"
260
261    with pytest.raises(exceptions.OAuthError) as excinfo:
262        utils.handle_error_response(response_data)
263
264    assert excinfo.match(r"Oops, something wrong happened")
265