1 # Copyright (c) 2009 Google Inc. All rights reserved.
2 # Copyright (c) 2009 Apple Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 # Python module for reading stored web credentials from the OS.
36 from webkitpy.common.checkout.scm import Git
37 from webkitpy.common.system.executive import Executive, ScriptError
38 from webkitpy.common.system.user import User
39 from webkitpy.common.system.deprecated_logging import log
42 # Use keyring, a cross platform keyring interface, as a fallback:
43 # http://pypi.python.org/pypi/keyring
49 class Credentials(object):
50 _environ_prefix = "webkit_bugzilla_"
52 def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd(),
55 self.git_prefix = "%s." % git_prefix if git_prefix else ""
56 self.executive = executive or Executive()
58 self._keyring = keyring
60 def _credentials_from_git(self):
62 if not Git.in_working_directory(self.cwd):
64 return (Git.read_git_config(self.git_prefix + "username"),
65 Git.read_git_config(self.git_prefix + "password"))
67 # Catch and ignore OSError exceptions such as "no such file
68 # or directory" (OSError errno 2), which imply that the Git
69 # command cannot be found/is not installed.
73 def _keychain_value_with_label(self, label, source_text):
74 match = re.search("%s\"(?P<value>.+)\"" % label,
78 return match.group('value')
80 def _is_mac_os_x(self):
81 return platform.mac_ver()[0]
83 def _parse_security_tool_output(self, security_output):
84 username = self._keychain_value_with_label("^\s*\"acct\"<blob>=",
86 password = self._keychain_value_with_label("^password: ",
88 return [username, password]
90 def _run_security_tool(self, username=None):
93 "find-internet-password",
99 security_command += ["-a", username]
101 log("Reading Keychain for %s account and password. "
102 "Click \"Allow\" to continue..." % self.host)
104 return self.executive.run_command(security_command)
106 # Failed to either find a keychain entry or somekind of OS-related
107 # error occured (for instance, couldn't find the /usr/sbin/security
109 log("Could not find a keychain entry for %s." % self.host)
112 def _credentials_from_keychain(self, username=None):
113 if not self._is_mac_os_x():
114 return [username, None]
116 security_output = self._run_security_tool(username)
118 return self._parse_security_tool_output(security_output)
122 def _read_environ(self, key):
123 environ_key = self._environ_prefix + key
124 return os.environ.get(environ_key.upper())
126 def _credentials_from_environment(self):
127 return (self._read_environ("username"), self._read_environ("password"))
129 def _offer_to_store_credentials_in_keyring(self, username, password):
130 if not self._keyring:
132 if not User().confirm("Store password in system keyring?", User.DEFAULT_NO):
134 self._keyring.set_password(self.host, username, password)
136 def read_credentials(self):
137 username, password = self._credentials_from_environment()
138 # FIXME: We don't currently support pulling the username from one
139 # source and the password from a separate source.
140 if not username or not password:
141 username, password = self._credentials_from_git()
142 if not username or not password:
143 username, password = self._credentials_from_keychain(username)
145 if username and not password and self._keyring:
146 password = self._keyring.get_password(self.host, username)
149 username = User.prompt("%s login: " % self.host)
151 password = User.prompt_password("%s password for %s: " % (self.host, username))
152 self._offer_to_store_credentials_in_keyring(username, password)
154 return (username, password)