|
1 # Copyright (c) 2009 Google Inc. All rights reserved. |
|
2 # Copyright (c) 2009 Apple Inc. All rights reserved. |
|
3 # |
|
4 # Redistribution and use in source and binary forms, with or without |
|
5 # modification, are permitted provided that the following conditions are |
|
6 # met: |
|
7 # |
|
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 |
|
13 # distribution. |
|
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. |
|
17 # |
|
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. |
|
29 # |
|
30 # Python module for reading stored web credentials from the OS. |
|
31 |
|
32 import getpass |
|
33 import os |
|
34 import platform |
|
35 import re |
|
36 |
|
37 from webkitpy.common.checkout.scm import Git |
|
38 from webkitpy.common.system.executive import Executive, ScriptError |
|
39 from webkitpy.common.system.user import User |
|
40 from webkitpy.common.system.deprecated_logging import log |
|
41 |
|
42 |
|
43 class Credentials(object): |
|
44 |
|
45 def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd()): |
|
46 self.host = host |
|
47 self.git_prefix = "%s." % git_prefix if git_prefix else "" |
|
48 self.executive = executive or Executive() |
|
49 self.cwd = cwd |
|
50 |
|
51 def _credentials_from_git(self): |
|
52 return [Git.read_git_config(self.git_prefix + "username"), |
|
53 Git.read_git_config(self.git_prefix + "password")] |
|
54 |
|
55 def _keychain_value_with_label(self, label, source_text): |
|
56 match = re.search("%s\"(?P<value>.+)\"" % label, |
|
57 source_text, |
|
58 re.MULTILINE) |
|
59 if match: |
|
60 return match.group('value') |
|
61 |
|
62 def _is_mac_os_x(self): |
|
63 return platform.mac_ver()[0] |
|
64 |
|
65 def _parse_security_tool_output(self, security_output): |
|
66 username = self._keychain_value_with_label("^\s*\"acct\"<blob>=", |
|
67 security_output) |
|
68 password = self._keychain_value_with_label("^password: ", |
|
69 security_output) |
|
70 return [username, password] |
|
71 |
|
72 def _run_security_tool(self, username=None): |
|
73 security_command = [ |
|
74 "/usr/bin/security", |
|
75 "find-internet-password", |
|
76 "-g", |
|
77 "-s", |
|
78 self.host, |
|
79 ] |
|
80 if username: |
|
81 security_command += ["-a", username] |
|
82 |
|
83 log("Reading Keychain for %s account and password. " |
|
84 "Click \"Allow\" to continue..." % self.host) |
|
85 try: |
|
86 return self.executive.run_command(security_command) |
|
87 except ScriptError: |
|
88 # Failed to either find a keychain entry or somekind of OS-related |
|
89 # error occured (for instance, couldn't find the /usr/sbin/security |
|
90 # command). |
|
91 log("Could not find a keychain entry for %s." % self.host) |
|
92 return None |
|
93 |
|
94 def _credentials_from_keychain(self, username=None): |
|
95 if not self._is_mac_os_x(): |
|
96 return [username, None] |
|
97 |
|
98 security_output = self._run_security_tool(username) |
|
99 if security_output: |
|
100 return self._parse_security_tool_output(security_output) |
|
101 else: |
|
102 return [None, None] |
|
103 |
|
104 def read_credentials(self): |
|
105 username = None |
|
106 password = None |
|
107 |
|
108 try: |
|
109 if Git.in_working_directory(self.cwd): |
|
110 (username, password) = self._credentials_from_git() |
|
111 except OSError, e: |
|
112 # Catch and ignore OSError exceptions such as "no such file |
|
113 # or directory" (OSError errno 2), which imply that the Git |
|
114 # command cannot be found/is not installed. |
|
115 pass |
|
116 |
|
117 if not username or not password: |
|
118 (username, password) = self._credentials_from_keychain(username) |
|
119 |
|
120 if not username: |
|
121 username = User.prompt("%s login: " % self.host) |
|
122 if not password: |
|
123 password = getpass.getpass("%s password for %s: " % (self.host, |
|
124 username)) |
|
125 |
|
126 return [username, password] |