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