1# Copyright 2014 Google Inc.
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
16import os
17
18import mock
19import pytest
20
21from google.auth import environment_vars
22from google.auth import exceptions
23from google.auth import transport
24from google.oauth2 import id_token
25from google.oauth2 import service_account
26
27SERVICE_ACCOUNT_FILE = os.path.join(
28    os.path.dirname(__file__), "../data/service_account.json"
29)
30ID_TOKEN_AUDIENCE = "https://pubsub.googleapis.com"
31
32
33def make_request(status, data=None):
34    response = mock.create_autospec(transport.Response, instance=True)
35    response.status = status
36
37    if data is not None:
38        response.data = json.dumps(data).encode("utf-8")
39
40    request = mock.create_autospec(transport.Request)
41    request.return_value = response
42    return request
43
44
45def test__fetch_certs_success():
46    certs = {"1": "cert"}
47    request = make_request(200, certs)
48
49    returned_certs = id_token._fetch_certs(request, mock.sentinel.cert_url)
50
51    request.assert_called_once_with(mock.sentinel.cert_url, method="GET")
52    assert returned_certs == certs
53
54
55def test__fetch_certs_failure():
56    request = make_request(404)
57
58    with pytest.raises(exceptions.TransportError):
59        id_token._fetch_certs(request, mock.sentinel.cert_url)
60
61    request.assert_called_once_with(mock.sentinel.cert_url, method="GET")
62
63
64@mock.patch("google.auth.jwt.decode", autospec=True)
65@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True)
66def test_verify_token(_fetch_certs, decode):
67    result = id_token.verify_token(mock.sentinel.token, mock.sentinel.request)
68
69    assert result == decode.return_value
70    _fetch_certs.assert_called_once_with(
71        mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL
72    )
73    decode.assert_called_once_with(
74        mock.sentinel.token,
75        certs=_fetch_certs.return_value,
76        audience=None,
77        clock_skew_in_seconds=0,
78    )
79
80
81@mock.patch("google.auth.jwt.decode", autospec=True)
82@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True)
83def test_verify_token_args(_fetch_certs, decode):
84    result = id_token.verify_token(
85        mock.sentinel.token,
86        mock.sentinel.request,
87        audience=mock.sentinel.audience,
88        certs_url=mock.sentinel.certs_url,
89    )
90
91    assert result == decode.return_value
92    _fetch_certs.assert_called_once_with(mock.sentinel.request, mock.sentinel.certs_url)
93    decode.assert_called_once_with(
94        mock.sentinel.token,
95        certs=_fetch_certs.return_value,
96        audience=mock.sentinel.audience,
97        clock_skew_in_seconds=0,
98    )
99
100
101@mock.patch("google.auth.jwt.decode", autospec=True)
102@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True)
103def test_verify_token_clock_skew(_fetch_certs, decode):
104    result = id_token.verify_token(
105        mock.sentinel.token,
106        mock.sentinel.request,
107        audience=mock.sentinel.audience,
108        certs_url=mock.sentinel.certs_url,
109        clock_skew_in_seconds=10,
110    )
111
112    assert result == decode.return_value
113    _fetch_certs.assert_called_once_with(mock.sentinel.request, mock.sentinel.certs_url)
114    decode.assert_called_once_with(
115        mock.sentinel.token,
116        certs=_fetch_certs.return_value,
117        audience=mock.sentinel.audience,
118        clock_skew_in_seconds=10,
119    )
120
121
122@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
123def test_verify_oauth2_token(verify_token):
124    verify_token.return_value = {"iss": "accounts.google.com"}
125    result = id_token.verify_oauth2_token(
126        mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience
127    )
128
129    assert result == verify_token.return_value
130    verify_token.assert_called_once_with(
131        mock.sentinel.token,
132        mock.sentinel.request,
133        audience=mock.sentinel.audience,
134        certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL,
135        clock_skew_in_seconds=0,
136    )
137
138
139@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
140def test_verify_oauth2_token_clock_skew(verify_token):
141    verify_token.return_value = {"iss": "accounts.google.com"}
142    result = id_token.verify_oauth2_token(
143        mock.sentinel.token,
144        mock.sentinel.request,
145        audience=mock.sentinel.audience,
146        clock_skew_in_seconds=10,
147    )
148
149    assert result == verify_token.return_value
150    verify_token.assert_called_once_with(
151        mock.sentinel.token,
152        mock.sentinel.request,
153        audience=mock.sentinel.audience,
154        certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL,
155        clock_skew_in_seconds=10,
156    )
157
158
159@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
160def test_verify_oauth2_token_invalid_iss(verify_token):
161    verify_token.return_value = {"iss": "invalid_issuer"}
162
163    with pytest.raises(exceptions.GoogleAuthError):
164        id_token.verify_oauth2_token(
165            mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience
166        )
167
168
169@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
170def test_verify_firebase_token(verify_token):
171    result = id_token.verify_firebase_token(
172        mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience
173    )
174
175    assert result == verify_token.return_value
176    verify_token.assert_called_once_with(
177        mock.sentinel.token,
178        mock.sentinel.request,
179        audience=mock.sentinel.audience,
180        certs_url=id_token._GOOGLE_APIS_CERTS_URL,
181        clock_skew_in_seconds=0,
182    )
183
184
185@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
186def test_verify_firebase_token_clock_skew(verify_token):
187    result = id_token.verify_firebase_token(
188        mock.sentinel.token,
189        mock.sentinel.request,
190        audience=mock.sentinel.audience,
191        clock_skew_in_seconds=10,
192    )
193
194    assert result == verify_token.return_value
195    verify_token.assert_called_once_with(
196        mock.sentinel.token,
197        mock.sentinel.request,
198        audience=mock.sentinel.audience,
199        certs_url=id_token._GOOGLE_APIS_CERTS_URL,
200        clock_skew_in_seconds=10,
201    )
202
203
204def test_fetch_id_token_credentials_optional_request(monkeypatch):
205    monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False)
206
207    # Test a request object is created if not provided
208    with mock.patch("google.auth.compute_engine._metadata.ping", return_value=True):
209        with mock.patch(
210            "google.auth.compute_engine.IDTokenCredentials.__init__", return_value=None
211        ):
212            with mock.patch(
213                "google.auth.transport.requests.Request.__init__", return_value=None
214            ) as mock_request:
215                id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
216            mock_request.assert_called()
217
218
219def test_fetch_id_token_credentials_from_metadata_server(monkeypatch):
220    monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False)
221
222    mock_req = mock.Mock()
223
224    with mock.patch("google.auth.compute_engine._metadata.ping", return_value=True):
225        with mock.patch(
226            "google.auth.compute_engine.IDTokenCredentials.__init__", return_value=None
227        ) as mock_init:
228            id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE, request=mock_req)
229        mock_init.assert_called_once_with(
230            mock_req, ID_TOKEN_AUDIENCE, use_metadata_identity_endpoint=True
231        )
232
233
234def test_fetch_id_token_credentials_from_explicit_cred_json_file(monkeypatch):
235    monkeypatch.setenv(environment_vars.CREDENTIALS, SERVICE_ACCOUNT_FILE)
236
237    cred = id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
238    assert isinstance(cred, service_account.IDTokenCredentials)
239    assert cred._target_audience == ID_TOKEN_AUDIENCE
240
241
242def test_fetch_id_token_credentials_no_cred_exists(monkeypatch):
243    monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False)
244
245    with mock.patch(
246        "google.auth.compute_engine._metadata.ping",
247        side_effect=exceptions.TransportError(),
248    ):
249        with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
250            id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
251        assert excinfo.match(
252            r"Neither metadata server or valid service account credentials are found."
253        )
254
255    with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False):
256        with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
257            id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
258        assert excinfo.match(
259            r"Neither metadata server or valid service account credentials are found."
260        )
261
262
263def test_fetch_id_token_credentials_invalid_cred_file_type(monkeypatch):
264    user_credentials_file = os.path.join(
265        os.path.dirname(__file__), "../data/authorized_user.json"
266    )
267    monkeypatch.setenv(environment_vars.CREDENTIALS, user_credentials_file)
268
269    with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False):
270        with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
271            id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
272        assert excinfo.match(
273            r"Neither metadata server or valid service account credentials are found."
274        )
275
276
277def test_fetch_id_token_credentials_invalid_json(monkeypatch):
278    not_json_file = os.path.join(os.path.dirname(__file__), "../data/public_cert.pem")
279    monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file)
280
281    with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
282        id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
283    assert excinfo.match(
284        r"GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials."
285    )
286
287
288def test_fetch_id_token_credentials_invalid_cred_path(monkeypatch):
289    not_json_file = os.path.join(os.path.dirname(__file__), "../data/not_exists.json")
290    monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file)
291
292    with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
293        id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE)
294    assert excinfo.match(
295        r"GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
296    )
297
298
299def test_fetch_id_token(monkeypatch):
300    mock_cred = mock.MagicMock()
301    mock_cred.token = "token"
302
303    mock_req = mock.Mock()
304
305    with mock.patch(
306        "google.oauth2.id_token.fetch_id_token_credentials", return_value=mock_cred
307    ) as mock_fetch:
308        token = id_token.fetch_id_token(mock_req, ID_TOKEN_AUDIENCE)
309    mock_fetch.assert_called_once_with(ID_TOKEN_AUDIENCE, request=mock_req)
310    mock_cred.refresh.assert_called_once_with(mock_req)
311    assert token == "token"
312