Home | History | Annotate | Download | only in net
      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