1 # Copyright (C) 2009 Google Inc. All rights reserved. 2 # 3 # Redistribution and use in source and binary forms, with or without 4 # modification, are permitted provided that the following conditions are 5 # met: 6 # 7 # * Redistributions of source code must retain the above copyright 8 # notice, this list of conditions and the following disclaimer. 9 # * Redistributions in binary form must reproduce the above 10 # copyright notice, this list of conditions and the following disclaimer 11 # in the documentation and/or other materials provided with the 12 # distribution. 13 # * Neither the name of Google Inc. nor the names of its 14 # contributors may be used to endorse or promote products derived from 15 # this software without specific prior written permission. 16 # 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 import os 30 import tempfile 31 import webkitpy.thirdparty.unittest2 as unittest 32 from webkitpy.common.net.credentials import Credentials 33 from webkitpy.common.system.executive import Executive 34 from webkitpy.common.system.outputcapture import OutputCapture 35 from webkitpy.common.system.user_mock import MockUser 36 from webkitpy.thirdparty.mock import Mock 37 from webkitpy.tool.mocktool import MockOptions 38 from webkitpy.common.system.executive_mock import MockExecutive 39 40 41 # FIXME: Other unit tests probably want this class. 42 class _TemporaryDirectory(object): 43 def __init__(self, **kwargs): 44 self._kwargs = kwargs 45 self._directory_path = None 46 47 def __enter__(self): 48 self._directory_path = tempfile.mkdtemp(**self._kwargs) 49 return self._directory_path 50 51 def __exit__(self, type, value, traceback): 52 os.rmdir(self._directory_path) 53 54 55 # Note: All tests should use this class instead of Credentials directly to avoid using a real Executive. 56 class MockedCredentials(Credentials): 57 def __init__(self, *args, **kwargs): 58 if 'executive' not in kwargs: 59 kwargs['executive'] = MockExecutive() 60 Credentials.__init__(self, *args, **kwargs) 61 62 63 class CredentialsTest(unittest.TestCase): 64 example_security_output = """keychain: "/Users/test/Library/Keychains/login.keychain" 65 class: "inet" 66 attributes: 67 0x00000007 <blob>="bugs.webkit.org (test@webkit.org)" 68 0x00000008 <blob>=<NULL> 69 "acct"<blob>="test@webkit.org" 70 "atyp"<blob>="form" 71 "cdat"<timedate>=0x32303039303832353233353231365A00 "20090825235216Z\000" 72 "crtr"<uint32>=<NULL> 73 "cusi"<sint32>=<NULL> 74 "desc"<blob>="Web form password" 75 "icmt"<blob>="default" 76 "invi"<sint32>=<NULL> 77 "mdat"<timedate>=0x32303039303930393137323635315A00 "20090909172651Z\000" 78 "nega"<sint32>=<NULL> 79 "path"<blob>=<NULL> 80 "port"<uint32>=0x00000000 81 "prot"<blob>=<NULL> 82 "ptcl"<uint32>="htps" 83 "scrp"<sint32>=<NULL> 84 "sdmn"<blob>=<NULL> 85 "srvr"<blob>="bugs.webkit.org" 86 "type"<uint32>=<NULL> 87 password: "SECRETSAUCE" 88 """ 89 90 def test_keychain_lookup_on_non_mac(self): 91 class FakeCredentials(MockedCredentials): 92 def _is_mac_os_x(self): 93 return False 94 credentials = FakeCredentials("bugs.webkit.org") 95 self.assertFalse(credentials._is_mac_os_x()) 96 self.assertEqual(credentials._credentials_from_keychain("foo"), ["foo", None]) 97 98 def test_security_output_parse(self): 99 credentials = MockedCredentials("bugs.webkit.org") 100 self.assertEqual(credentials._parse_security_tool_output(self.example_security_output), ["test (at] webkit.org", "SECRETSAUCE"]) 101 102 def test_security_output_parse_entry_not_found(self): 103 # FIXME: This test won't work if the user has a credential for foo.example.com! 104 credentials = Credentials("foo.example.com") 105 if not credentials._is_mac_os_x(): 106 return # This test does not run on a non-Mac. 107 108 # Note, we ignore the captured output because it is already covered 109 # by the test case CredentialsTest._assert_security_call (below). 110 outputCapture = OutputCapture() 111 outputCapture.capture_output() 112 self.assertIsNone(credentials._run_security_tool()) 113 outputCapture.restore_output() 114 115 def _assert_security_call(self, username=None): 116 executive_mock = Mock() 117 credentials = MockedCredentials("example.com", executive=executive_mock) 118 119 expected_logs = "Reading Keychain for example.com account and password. Click \"Allow\" to continue...\n" 120 OutputCapture().assert_outputs(self, credentials._run_security_tool, [username], expected_logs=expected_logs) 121 122 security_args = ["/usr/bin/security", "find-internet-password", "-g", "-s", "example.com"] 123 if username: 124 security_args += ["-a", username] 125 executive_mock.run_command.assert_called_with(security_args) 126 127 def test_security_calls(self): 128 self._assert_security_call() 129 self._assert_security_call(username="foo") 130 131 def test_credentials_from_environment(self): 132 credentials = MockedCredentials("example.com") 133 134 saved_environ = os.environ.copy() 135 os.environ['WEBKIT_BUGZILLA_USERNAME'] = "foo" 136 os.environ['WEBKIT_BUGZILLA_PASSWORD'] = "bar" 137 username, password = credentials._credentials_from_environment() 138 self.assertEqual(username, "foo") 139 self.assertEqual(password, "bar") 140 os.environ = saved_environ 141 142 def test_read_credentials_without_git_repo(self): 143 # FIXME: This should share more code with test_keyring_without_git_repo 144 class FakeCredentials(MockedCredentials): 145 def _is_mac_os_x(self): 146 return True 147 148 def _credentials_from_keychain(self, username): 149 return ("test (at] webkit.org", "SECRETSAUCE") 150 151 def _credentials_from_environment(self): 152 return (None, None) 153 154 with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: 155 credentials = FakeCredentials("bugs.webkit.org", cwd=temp_dir_path) 156 # FIXME: Using read_credentials here seems too broad as higher-priority 157 # credential source could be affected by the user's environment. 158 self.assertEqual(credentials.read_credentials(), ("test (at] webkit.org", "SECRETSAUCE")) 159 160 161 def test_keyring_without_git_repo(self): 162 # FIXME: This should share more code with test_read_credentials_without_git_repo 163 class MockKeyring(object): 164 def get_password(self, host, username): 165 return "NOMNOMNOM" 166 167 class FakeCredentials(MockedCredentials): 168 def _is_mac_os_x(self): 169 return True 170 171 def _credentials_from_keychain(self, username): 172 return ("test (at] webkit.org", None) 173 174 def _credentials_from_environment(self): 175 return (None, None) 176 177 with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: 178 credentials = FakeCredentials("fake.hostname", cwd=temp_dir_path, keyring=MockKeyring()) 179 # FIXME: Using read_credentials here seems too broad as higher-priority 180 # credential source could be affected by the user's environment. 181 self.assertEqual(credentials.read_credentials(), ("test (at] webkit.org", "NOMNOMNOM")) 182 183 def test_keyring_without_git_repo_nor_keychain(self): 184 class MockKeyring(object): 185 def get_password(self, host, username): 186 return "NOMNOMNOM" 187 188 class FakeCredentials(MockedCredentials): 189 def _credentials_from_keychain(self, username): 190 return (None, None) 191 192 def _credentials_from_environment(self): 193 return (None, None) 194 195 class FakeUser(MockUser): 196 @classmethod 197 def prompt(cls, message, repeat=1, raw_input=raw_input): 198 return "test (at] webkit.org" 199 200 @classmethod 201 def prompt_password(cls, message, repeat=1, raw_input=raw_input): 202 raise AssertionError("should not prompt for password") 203 204 with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: 205 credentials = FakeCredentials("fake.hostname", cwd=temp_dir_path, keyring=MockKeyring()) 206 # FIXME: Using read_credentials here seems too broad as higher-priority 207 # credential source could be affected by the user's environment. 208 self.assertEqual(credentials.read_credentials(FakeUser), ("test (at] webkit.org", "NOMNOMNOM")) 209