1# Copyright 2021 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 15"""Tests for the reauth module.""" 16 17import base64 18import sys 19 20import mock 21import pytest 22import pyu2f 23 24from google.auth import exceptions 25from google.oauth2 import challenges 26 27 28def test_get_user_password(): 29 with mock.patch("getpass.getpass", return_value="foo"): 30 assert challenges.get_user_password("") == "foo" 31 32 33def test_security_key(): 34 metadata = { 35 "status": "READY", 36 "challengeId": 2, 37 "challengeType": "SECURITY_KEY", 38 "securityKey": { 39 "applicationId": "security_key_application_id", 40 "challenges": [ 41 { 42 "keyHandle": "some_key", 43 "challenge": base64.urlsafe_b64encode( 44 "some_challenge".encode("ascii") 45 ).decode("ascii"), 46 } 47 ], 48 }, 49 } 50 mock_key = mock.Mock() 51 52 challenge = challenges.SecurityKeyChallenge() 53 54 # Test the case that security key challenge is passed. 55 with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): 56 with mock.patch( 57 "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" 58 ) as mock_authenticate: 59 mock_authenticate.return_value = "security key response" 60 assert challenge.name == "SECURITY_KEY" 61 assert challenge.is_locally_eligible 62 assert challenge.obtain_challenge_input(metadata) == { 63 "securityKey": "security key response" 64 } 65 mock_authenticate.assert_called_with( 66 "security_key_application_id", 67 [{"key": mock_key, "challenge": b"some_challenge"}], 68 print_callback=sys.stderr.write, 69 ) 70 71 # Test various types of exceptions. 72 with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): 73 with mock.patch( 74 "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" 75 ) as mock_authenticate: 76 mock_authenticate.side_effect = pyu2f.errors.U2FError( 77 pyu2f.errors.U2FError.DEVICE_INELIGIBLE 78 ) 79 assert challenge.obtain_challenge_input(metadata) is None 80 81 with mock.patch( 82 "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" 83 ) as mock_authenticate: 84 mock_authenticate.side_effect = pyu2f.errors.U2FError( 85 pyu2f.errors.U2FError.TIMEOUT 86 ) 87 assert challenge.obtain_challenge_input(metadata) is None 88 89 with mock.patch( 90 "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" 91 ) as mock_authenticate: 92 mock_authenticate.side_effect = pyu2f.errors.U2FError( 93 pyu2f.errors.U2FError.BAD_REQUEST 94 ) 95 with pytest.raises(pyu2f.errors.U2FError): 96 challenge.obtain_challenge_input(metadata) 97 98 with mock.patch( 99 "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" 100 ) as mock_authenticate: 101 mock_authenticate.side_effect = pyu2f.errors.NoDeviceFoundError() 102 assert challenge.obtain_challenge_input(metadata) is None 103 104 with mock.patch( 105 "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" 106 ) as mock_authenticate: 107 mock_authenticate.side_effect = pyu2f.errors.UnsupportedVersionException() 108 with pytest.raises(pyu2f.errors.UnsupportedVersionException): 109 challenge.obtain_challenge_input(metadata) 110 111 with mock.patch.dict("sys.modules"): 112 sys.modules["pyu2f"] = None 113 with pytest.raises(exceptions.ReauthFailError) as excinfo: 114 challenge.obtain_challenge_input(metadata) 115 assert excinfo.match(r"pyu2f dependency is required") 116 117 118@mock.patch("getpass.getpass", return_value="foo") 119def test_password_challenge(getpass_mock): 120 challenge = challenges.PasswordChallenge() 121 122 with mock.patch("getpass.getpass", return_value="foo"): 123 assert challenge.is_locally_eligible 124 assert challenge.name == "PASSWORD" 125 assert challenges.PasswordChallenge().obtain_challenge_input({}) == { 126 "credential": "foo" 127 } 128 129 with mock.patch("getpass.getpass", return_value=None): 130 assert challenges.PasswordChallenge().obtain_challenge_input({}) == { 131 "credential": " " 132 } 133 134 135def test_saml_challenge(): 136 challenge = challenges.SamlChallenge() 137 assert challenge.is_locally_eligible 138 assert challenge.name == "SAML" 139 with pytest.raises(exceptions.ReauthSamlChallengeFailError): 140 challenge.obtain_challenge_input(None) 141