##// END OF EJS Templates
Some more demandimport ignores
Marcin Kasperski -
r106:4ea8e9e1 default
parent child Browse files
Show More
@@ -1,472 +1,475 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 #
2 #
3 # mercurial_keyring: save passwords in password database
3 # mercurial_keyring: save passwords in password database
4 #
4 #
5 # Copyright (c) 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
5 # Copyright (c) 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
6 # All rights reserved.
6 # All rights reserved.
7 #
7 #
8 # Redistribution and use in source and binary forms, with or without
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
9 # modification, are permitted provided that the following conditions
10 # are met:
10 # are met:
11 # 1. Redistributions of source code must retain the above copyright
11 # 1. Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
12 # notice, this list of conditions and the following disclaimer.
13 # 2. Redistributions in binary form must reproduce the above copyright
13 # 2. Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
15 # documentation and/or other materials provided with the distribution.
16 # 3. The name of the author may not be used to endorse or promote products
16 # 3. The name of the author may not be used to endorse or promote products
17 # derived from this software without specific prior written permission.
17 # derived from this software without specific prior written permission.
18 #
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #
29 #
30 # See README.txt for more details.
30 # See README.txt for more details.
31
31
32 ''' securely save HTTP and SMTP authentication details
32 ''' securely save HTTP and SMTP authentication details
33 mercurial_keyring is a Mercurial extension used to securely save
33 mercurial_keyring is a Mercurial extension used to securely save
34 HTTP and SMTP authentication passwords in password databases (Gnome
34 HTTP and SMTP authentication passwords in password databases (Gnome
35 Keyring, KDE KWallet, OSXKeyChain, specific solutions for Win32 and
35 Keyring, KDE KWallet, OSXKeyChain, specific solutions for Win32 and
36 command line). This extension uses and wraps services of the keyring
36 command line). This extension uses and wraps services of the keyring
37 library.
37 library.
38 '''
38 '''
39
39
40 from mercurial import util
40 from mercurial import util
41 from mercurial.i18n import _
41 from mercurial.i18n import _
42 try:
42 try:
43 from mercurial.url import passwordmgr
43 from mercurial.url import passwordmgr
44 except:
44 except:
45 from mercurial.httprepo import passwordmgr
45 from mercurial.httprepo import passwordmgr
46 from mercurial import mail
46 from mercurial import mail
47 from urllib2 import AbstractBasicAuthHandler, AbstractDigestAuthHandler
47 from urllib2 import AbstractBasicAuthHandler, AbstractDigestAuthHandler
48
48
49 # mercurial.demandimport incompatibility workaround,
49 # mercurial.demandimport incompatibility workaround,
50 # would cause gnomekeyring, one of the possible
50 # would cause gnomekeyring, one of the possible
51 # keyring backends, not to work.
51 # keyring backends, not to work.
52 from mercurial.demandimport import ignore
52 from mercurial.demandimport import ignore
53 if "gobject._gobject" not in ignore:
53 if "gobject._gobject" not in ignore:
54 ignore.append("gobject._gobject")
54 ignore.append("gobject._gobject")
55 ignore.append("configparser")
55 ignore.append("configparser")
56 ignore.append("json")
57 ignore.append("abc")
58 ignore.append("io")
56
59
57 import keyring
60 import keyring
58 from urlparse import urlparse
61 from urlparse import urlparse
59 import urllib2
62 import urllib2
60 import smtplib, socket
63 import smtplib, socket
61 import os
64 import os
62
65
63 KEYRING_SERVICE = "Mercurial"
66 KEYRING_SERVICE = "Mercurial"
64
67
65 ############################################################
68 ############################################################
66
69
67 def monkeypatch_method(cls,fname=None):
70 def monkeypatch_method(cls,fname=None):
68 def decorator(func):
71 def decorator(func):
69 local_fname = fname
72 local_fname = fname
70 if local_fname is None:
73 if local_fname is None:
71 local_fname = func.__name__
74 local_fname = func.__name__
72 setattr(func, "orig", getattr(cls, local_fname, None))
75 setattr(func, "orig", getattr(cls, local_fname, None))
73 setattr(cls, local_fname, func)
76 setattr(cls, local_fname, func)
74 return func
77 return func
75 return decorator
78 return decorator
76
79
77 ############################################################
80 ############################################################
78
81
79 class PasswordStore(object):
82 class PasswordStore(object):
80 """
83 """
81 Helper object handling keyring usage (password save&restore,
84 Helper object handling keyring usage (password save&restore,
82 the way passwords are keyed in the keyring).
85 the way passwords are keyed in the keyring).
83 """
86 """
84 def __init__(self):
87 def __init__(self):
85 self.cache = dict()
88 self.cache = dict()
86 def get_http_password(self, url, username):
89 def get_http_password(self, url, username):
87 return keyring.get_password(KEYRING_SERVICE,
90 return keyring.get_password(KEYRING_SERVICE,
88 self._format_http_key(url, username))
91 self._format_http_key(url, username))
89 def set_http_password(self, url, username, password):
92 def set_http_password(self, url, username, password):
90 keyring.set_password(KEYRING_SERVICE,
93 keyring.set_password(KEYRING_SERVICE,
91 self._format_http_key(url, username),
94 self._format_http_key(url, username),
92 password)
95 password)
93 def clear_http_password(self, url, username):
96 def clear_http_password(self, url, username):
94 self.set_http_password(url, username, "")
97 self.set_http_password(url, username, "")
95 def _format_http_key(self, url, username):
98 def _format_http_key(self, url, username):
96 return "%s@@%s" % (username, url)
99 return "%s@@%s" % (username, url)
97 def get_smtp_password(self, machine, port, username):
100 def get_smtp_password(self, machine, port, username):
98 return keyring.get_password(
101 return keyring.get_password(
99 KEYRING_SERVICE,
102 KEYRING_SERVICE,
100 self._format_smtp_key(machine, port, username))
103 self._format_smtp_key(machine, port, username))
101 def set_smtp_password(self, machine, port, username, password):
104 def set_smtp_password(self, machine, port, username, password):
102 keyring.set_password(
105 keyring.set_password(
103 KEYRING_SERVICE,
106 KEYRING_SERVICE,
104 self._format_smtp_key(machine, port, username),
107 self._format_smtp_key(machine, port, username),
105 password)
108 password)
106 def clear_smtp_password(self, machine, port, username):
109 def clear_smtp_password(self, machine, port, username):
107 self.set_smtp_password(machine, port, username, "")
110 self.set_smtp_password(machine, port, username, "")
108 def _format_smtp_key(self, machine, port, username):
111 def _format_smtp_key(self, machine, port, username):
109 return "%s@@%s:%s" % (username, machine, str(port))
112 return "%s@@%s:%s" % (username, machine, str(port))
110
113
111 password_store = PasswordStore()
114 password_store = PasswordStore()
112
115
113 ############################################################
116 ############################################################
114
117
115 def _debug(ui, msg):
118 def _debug(ui, msg):
116 ui.debug("[HgKeyring] " + msg + "\n")
119 ui.debug("[HgKeyring] " + msg + "\n")
117
120
118 def _debug_reply(ui, msg, url, user, pwd):
121 def _debug_reply(ui, msg, url, user, pwd):
119 _debug(ui, "%s. Url: %s, user: %s, passwd: %s" % (
122 _debug(ui, "%s. Url: %s, user: %s, passwd: %s" % (
120 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
123 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
121
124
122
125
123 ############################################################
126 ############################################################
124
127
125 class HTTPPasswordHandler(object):
128 class HTTPPasswordHandler(object):
126 """
129 """
127 Actual implementation of password handling (user prompting,
130 Actual implementation of password handling (user prompting,
128 configuration file searching, keyring save&restore).
131 configuration file searching, keyring save&restore).
129
132
130 Object of this class is bound as passwordmgr attribute.
133 Object of this class is bound as passwordmgr attribute.
131 """
134 """
132 def __init__(self):
135 def __init__(self):
133 self.pwd_cache = {}
136 self.pwd_cache = {}
134 self.last_reply = None
137 self.last_reply = None
135
138
136 def find_auth(self, pwmgr, realm, authuri, req):
139 def find_auth(self, pwmgr, realm, authuri, req):
137 """
140 """
138 Actual implementation of find_user_password - different
141 Actual implementation of find_user_password - different
139 ways of obtaining the username and password.
142 ways of obtaining the username and password.
140 """
143 """
141 ui = pwmgr.ui
144 ui = pwmgr.ui
142
145
143 # If we are called again just after identical previous
146 # If we are called again just after identical previous
144 # request, then the previously returned auth must have been
147 # request, then the previously returned auth must have been
145 # wrong. So we note this to force password prompt (and avoid
148 # wrong. So we note this to force password prompt (and avoid
146 # reusing bad password indifinitely).
149 # reusing bad password indifinitely).
147 after_bad_auth = (self.last_reply \
150 after_bad_auth = (self.last_reply \
148 and (self.last_reply['realm'] == realm) \
151 and (self.last_reply['realm'] == realm) \
149 and (self.last_reply['authuri'] == authuri) \
152 and (self.last_reply['authuri'] == authuri) \
150 and (self.last_reply['req'] == req))
153 and (self.last_reply['req'] == req))
151 if after_bad_auth:
154 if after_bad_auth:
152 _debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
155 _debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
153
156
154 # Strip arguments to get actual remote repository url.
157 # Strip arguments to get actual remote repository url.
155 base_url = self.canonical_url(authuri)
158 base_url = self.canonical_url(authuri)
156
159
157 # Extracting possible username (or password)
160 # Extracting possible username (or password)
158 # stored directly in repository url
161 # stored directly in repository url
159 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
162 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
160 pwmgr, realm, authuri)
163 pwmgr, realm, authuri)
161 if user and pwd:
164 if user and pwd:
162 _debug_reply(ui, _("Auth data found in repository URL"),
165 _debug_reply(ui, _("Auth data found in repository URL"),
163 base_url, user, pwd)
166 base_url, user, pwd)
164 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
167 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
165 return user, pwd
168 return user, pwd
166
169
167 # Loading .hg/hgrc [auth] section contents. If prefix is given,
170 # Loading .hg/hgrc [auth] section contents. If prefix is given,
168 # it will be used as a key to lookup password in the keyring.
171 # it will be used as a key to lookup password in the keyring.
169 auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url, user)
172 auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url, user)
170 if prefix_url:
173 if prefix_url:
171 keyring_url = prefix_url
174 keyring_url = prefix_url
172 else:
175 else:
173 keyring_url = base_url
176 keyring_url = base_url
174 _debug(ui, _("Keyring URL: %s") % keyring_url)
177 _debug(ui, _("Keyring URL: %s") % keyring_url)
175
178
176 # Checking the memory cache (there may be many http calls per command)
179 # Checking the memory cache (there may be many http calls per command)
177 cache_key = (realm, keyring_url)
180 cache_key = (realm, keyring_url)
178 if not after_bad_auth:
181 if not after_bad_auth:
179 cached_auth = self.pwd_cache.get(cache_key)
182 cached_auth = self.pwd_cache.get(cache_key)
180 if cached_auth:
183 if cached_auth:
181 user, pwd = cached_auth
184 user, pwd = cached_auth
182 _debug_reply(ui, _("Cached auth data found"),
185 _debug_reply(ui, _("Cached auth data found"),
183 base_url, user, pwd)
186 base_url, user, pwd)
184 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
187 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
185 return user, pwd
188 return user, pwd
186
189
187 if auth_user:
190 if auth_user:
188 if user and (user != auth_user):
191 if user and (user != auth_user):
189 raise util.Abort(_('mercurial_keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, user, auth_user)))
192 raise util.Abort(_('mercurial_keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, user, auth_user)))
190 user = auth_user
193 user = auth_user
191 if pwd:
194 if pwd:
192 self.pwd_cache[cache_key] = user, pwd
195 self.pwd_cache[cache_key] = user, pwd
193 _debug_reply(ui, _("Auth data set in .hg/hgrc"),
196 _debug_reply(ui, _("Auth data set in .hg/hgrc"),
194 base_url, user, pwd)
197 base_url, user, pwd)
195 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
198 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
196 return user, pwd
199 return user, pwd
197 else:
200 else:
198 _debug(ui, _("Username found in .hg/hgrc: %s") % user)
201 _debug(ui, _("Username found in .hg/hgrc: %s") % user)
199
202
200 # Loading password from keyring.
203 # Loading password from keyring.
201 # Only if username is known (so we know the key) and we are
204 # Only if username is known (so we know the key) and we are
202 # not after failure (so we don't reuse the bad password).
205 # not after failure (so we don't reuse the bad password).
203 if user and not after_bad_auth:
206 if user and not after_bad_auth:
204 _debug(ui, _("Looking for password for user %s and url %s") % (user, keyring_url))
207 _debug(ui, _("Looking for password for user %s and url %s") % (user, keyring_url))
205 pwd = password_store.get_http_password(keyring_url, user)
208 pwd = password_store.get_http_password(keyring_url, user)
206 if pwd:
209 if pwd:
207 self.pwd_cache[cache_key] = user, pwd
210 self.pwd_cache[cache_key] = user, pwd
208 _debug_reply(ui, _("Keyring password found"),
211 _debug_reply(ui, _("Keyring password found"),
209 base_url, user, pwd)
212 base_url, user, pwd)
210 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
213 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
211 return user, pwd
214 return user, pwd
212 else:
215 else:
213 _debug(ui, _("Password not present in the keyring"))
216 _debug(ui, _("Password not present in the keyring"))
214
217
215 # Is the username permanently set?
218 # Is the username permanently set?
216 fixed_user = (user and True or False)
219 fixed_user = (user and True or False)
217
220
218 # Last resort: interactive prompt
221 # Last resort: interactive prompt
219 if not ui.interactive():
222 if not ui.interactive():
220 raise util.Abort(_('mercurial_keyring: http authorization required but program used in non-interactive mode'))
223 raise util.Abort(_('mercurial_keyring: http authorization required but program used in non-interactive mode'))
221
224
222 if not fixed_user:
225 if not fixed_user:
223 ui.status(_("Username not specified in .hg/hgrc. Keyring will not be used.\n"))
226 ui.status(_("Username not specified in .hg/hgrc. Keyring will not be used.\n"))
224
227
225 ui.write(_("http authorization required\n"))
228 ui.write(_("http authorization required\n"))
226 ui.status(_("realm: %s\n") % realm)
229 ui.status(_("realm: %s\n") % realm)
227 if fixed_user:
230 if fixed_user:
228 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
231 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
229 else:
232 else:
230 user = ui.prompt(_("user:"), default=None)
233 user = ui.prompt(_("user:"), default=None)
231 pwd = ui.getpass(_("password: "))
234 pwd = ui.getpass(_("password: "))
232
235
233 if fixed_user:
236 if fixed_user:
234 # Saving password to the keyring.
237 # Saving password to the keyring.
235 # It is done only if username is permanently set.
238 # It is done only if username is permanently set.
236 # Otherwise we won't be able to find the password so it
239 # Otherwise we won't be able to find the password so it
237 # does not make much sense to preserve it
240 # does not make much sense to preserve it
238 _debug(ui, _("Saving password for %s to keyring") % user)
241 _debug(ui, _("Saving password for %s to keyring") % user)
239 password_store.set_http_password(keyring_url, user, pwd)
242 password_store.set_http_password(keyring_url, user, pwd)
240
243
241 # Saving password to the memory cache
244 # Saving password to the memory cache
242 self.pwd_cache[cache_key] = user, pwd
245 self.pwd_cache[cache_key] = user, pwd
243
246
244 _debug_reply(ui, _("Manually entered password"),
247 _debug_reply(ui, _("Manually entered password"),
245 base_url, user, pwd)
248 base_url, user, pwd)
246 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
249 self.last_reply = dict(realm=realm,authuri=authuri,user=user,req=req)
247 return user, pwd
250 return user, pwd
248
251
249 def load_hgrc_auth(self, ui, base_url, user):
252 def load_hgrc_auth(self, ui, base_url, user):
250 """
253 """
251 Loading [auth] section contents from local .hgrc
254 Loading [auth] section contents from local .hgrc
252
255
253 Returns (username, password, prefix) tuple (every
256 Returns (username, password, prefix) tuple (every
254 element can be None)
257 element can be None)
255 """
258 """
256 # Theoretically 3 lines below should do:
259 # Theoretically 3 lines below should do:
257
260
258 #auth_token = self.readauthtoken(base_url)
261 #auth_token = self.readauthtoken(base_url)
259 #if auth_token:
262 #if auth_token:
260 # user, pwd = auth.get('username'), auth.get('password')
263 # user, pwd = auth.get('username'), auth.get('password')
261
264
262 # Unfortunately they do not work, readauthtoken always return
265 # Unfortunately they do not work, readauthtoken always return
263 # None. Why? Because ui (self.ui of passwordmgr) describes the
266 # None. Why? Because ui (self.ui of passwordmgr) describes the
264 # *remote* repository, so does *not* contain any option from
267 # *remote* repository, so does *not* contain any option from
265 # local .hg/hgrc.
268 # local .hg/hgrc.
266
269
267 # TODO: mercurial 1.4.2 is claimed to resolve this problem
270 # TODO: mercurial 1.4.2 is claimed to resolve this problem
268 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
271 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
269 # so since this version workaround implemented below should
272 # so since this version workaround implemented below should
270 # not be necessary. As it will take some time until people
273 # not be necessary. As it will take some time until people
271 # migrate to >= 1.4.2, it would be best to implement
274 # migrate to >= 1.4.2, it would be best to implement
272 # workaround conditionally.
275 # workaround conditionally.
273
276
274 # Workaround: we recreate the repository object
277 # Workaround: we recreate the repository object
275 repo_root = ui.config("bundle", "mainreporoot")
278 repo_root = ui.config("bundle", "mainreporoot")
276
279
277 from mercurial.ui import ui as _ui
280 from mercurial.ui import ui as _ui
278 local_ui = _ui(ui)
281 local_ui = _ui(ui)
279 if repo_root:
282 if repo_root:
280 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
283 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
281 try:
284 try:
282 local_passwordmgr = passwordmgr(local_ui)
285 local_passwordmgr = passwordmgr(local_ui)
283 auth_token = local_passwordmgr.readauthtoken(base_url)
286 auth_token = local_passwordmgr.readauthtoken(base_url)
284 except AttributeError:
287 except AttributeError:
285 try:
288 try:
286 # hg 1.8
289 # hg 1.8
287 import mercurial.url
290 import mercurial.url
288 readauthforuri = mercurial.url.readauthforuri
291 readauthforuri = mercurial.url.readauthforuri
289 except (ImportError, AttributeError):
292 except (ImportError, AttributeError):
290 # hg 1.9
293 # hg 1.9
291 import mercurial.httpconnection
294 import mercurial.httpconnection
292 readauthforuri = mercurial.httpconnection.readauthforuri
295 readauthforuri = mercurial.httpconnection.readauthforuri
293 if readauthforuri.func_code.co_argcount == 3:
296 if readauthforuri.func_code.co_argcount == 3:
294 # Since hg.0593e8f81c71
297 # Since hg.0593e8f81c71
295 res = readauthforuri(local_ui, base_url, user)
298 res = readauthforuri(local_ui, base_url, user)
296 else:
299 else:
297 res = readauthforuri(local_ui, base_url)
300 res = readauthforuri(local_ui, base_url)
298 if res:
301 if res:
299 group, auth_token = res
302 group, auth_token = res
300 else:
303 else:
301 auth_token = None
304 auth_token = None
302 if auth_token:
305 if auth_token:
303 username = auth_token.get('username')
306 username = auth_token.get('username')
304 password = auth_token.get('password')
307 password = auth_token.get('password')
305 prefix = auth_token.get('prefix')
308 prefix = auth_token.get('prefix')
306 shortest_url = self.shortest_url(base_url, prefix)
309 shortest_url = self.shortest_url(base_url, prefix)
307 return username, password, shortest_url
310 return username, password, shortest_url
308
311
309 return None, None, None
312 return None, None, None
310
313
311 def shortest_url(self, base_url, prefix):
314 def shortest_url(self, base_url, prefix):
312 if not prefix or prefix == '*':
315 if not prefix or prefix == '*':
313 return base_url
316 return base_url
314 scheme, hostpath = base_url.split('://', 1)
317 scheme, hostpath = base_url.split('://', 1)
315 p = prefix.split('://', 1)
318 p = prefix.split('://', 1)
316 if len(p) > 1:
319 if len(p) > 1:
317 prefix_host_path = p[1]
320 prefix_host_path = p[1]
318 else:
321 else:
319 prefix_host_path = prefix
322 prefix_host_path = prefix
320 shortest_url = scheme + '://' + prefix_host_path
323 shortest_url = scheme + '://' + prefix_host_path
321 return shortest_url
324 return shortest_url
322
325
323 def canonical_url(self, authuri):
326 def canonical_url(self, authuri):
324 """
327 """
325 Strips query params from url. Used to convert urls like
328 Strips query params from url. Used to convert urls like
326 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
329 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
327 to
330 to
328 https://repo.machine.com/repos/apps/module
331 https://repo.machine.com/repos/apps/module
329 """
332 """
330 parsed_url = urlparse(authuri)
333 parsed_url = urlparse(authuri)
331 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
334 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
332 parsed_url.path)
335 parsed_url.path)
333
336
334 ############################################################
337 ############################################################
335
338
336 @monkeypatch_method(passwordmgr)
339 @monkeypatch_method(passwordmgr)
337 def find_user_password(self, realm, authuri):
340 def find_user_password(self, realm, authuri):
338 """
341 """
339 keyring-based implementation of username/password query
342 keyring-based implementation of username/password query
340 for HTTP(S) connections
343 for HTTP(S) connections
341
344
342 Passwords are saved in gnome keyring, OSX/Chain or other platform
345 Passwords are saved in gnome keyring, OSX/Chain or other platform
343 specific storage and keyed by the repository url
346 specific storage and keyed by the repository url
344 """
347 """
345 # Extend object attributes
348 # Extend object attributes
346 if not hasattr(self, '_pwd_handler'):
349 if not hasattr(self, '_pwd_handler'):
347 self._pwd_handler = HTTPPasswordHandler()
350 self._pwd_handler = HTTPPasswordHandler()
348
351
349 if hasattr(self, '_http_req'):
352 if hasattr(self, '_http_req'):
350 req = self._http_req
353 req = self._http_req
351 else:
354 else:
352 req = None
355 req = None
353
356
354 return self._pwd_handler.find_auth(self, realm, authuri, req)
357 return self._pwd_handler.find_auth(self, realm, authuri, req)
355
358
356 @monkeypatch_method(AbstractBasicAuthHandler, "http_error_auth_reqed")
359 @monkeypatch_method(AbstractBasicAuthHandler, "http_error_auth_reqed")
357 def basic_http_error_auth_reqed(self, authreq, host, req, headers):
360 def basic_http_error_auth_reqed(self, authreq, host, req, headers):
358 self.passwd._http_req = req
361 self.passwd._http_req = req
359 try:
362 try:
360 return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
363 return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
361 finally:
364 finally:
362 self.passwd._http_req = None
365 self.passwd._http_req = None
363
366
364 @monkeypatch_method(AbstractDigestAuthHandler, "http_error_auth_reqed")
367 @monkeypatch_method(AbstractDigestAuthHandler, "http_error_auth_reqed")
365 def digest_http_error_auth_reqed(self, authreq, host, req, headers):
368 def digest_http_error_auth_reqed(self, authreq, host, req, headers):
366 self.passwd._http_req = req
369 self.passwd._http_req = req
367 try:
370 try:
368 return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
371 return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
369 finally:
372 finally:
370 self.passwd._http_req = None
373 self.passwd._http_req = None
371
374
372 ############################################################
375 ############################################################
373
376
374 def try_smtp_login(ui, smtp_obj, username, password):
377 def try_smtp_login(ui, smtp_obj, username, password):
375 """
378 """
376 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
379 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
377 password.
380 password.
378
381
379 Returns:
382 Returns:
380 - True if login succeeded
383 - True if login succeeded
381 - False if login failed due to the wrong credentials
384 - False if login failed due to the wrong credentials
382
385
383 Throws Abort exception if login failed for any other reason.
386 Throws Abort exception if login failed for any other reason.
384
387
385 Immediately returns False if password is empty
388 Immediately returns False if password is empty
386 """
389 """
387 if not password:
390 if not password:
388 return False
391 return False
389 try:
392 try:
390 ui.note(_('(authenticating to mail server as %s)\n') %
393 ui.note(_('(authenticating to mail server as %s)\n') %
391 (username))
394 (username))
392 smtp_obj.login(username, password)
395 smtp_obj.login(username, password)
393 return True
396 return True
394 except smtplib.SMTPException, inst:
397 except smtplib.SMTPException, inst:
395 if inst.smtp_code == 535:
398 if inst.smtp_code == 535:
396 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
399 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
397 return False
400 return False
398 else:
401 else:
399 raise util.Abort(inst)
402 raise util.Abort(inst)
400
403
401 def keyring_supported_smtp(ui, username):
404 def keyring_supported_smtp(ui, username):
402 """
405 """
403 keyring-integrated replacement for mercurial.mail._smtp
406 keyring-integrated replacement for mercurial.mail._smtp
404 Used only when configuration file contains username, but
407 Used only when configuration file contains username, but
405 does not contain the password.
408 does not contain the password.
406
409
407 Most of the routine below is copied as-is from
410 Most of the routine below is copied as-is from
408 mercurial.mail._smtp. The only changed part is
411 mercurial.mail._smtp. The only changed part is
409 marked with #>>>>> and #<<<<< markers
412 marked with #>>>>> and #<<<<< markers
410 """
413 """
411 local_hostname = ui.config('smtp', 'local_hostname')
414 local_hostname = ui.config('smtp', 'local_hostname')
412 s = smtplib.SMTP(local_hostname=local_hostname)
415 s = smtplib.SMTP(local_hostname=local_hostname)
413 mailhost = ui.config('smtp', 'host')
416 mailhost = ui.config('smtp', 'host')
414 if not mailhost:
417 if not mailhost:
415 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
418 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
416 mailport = int(ui.config('smtp', 'port', 25))
419 mailport = int(ui.config('smtp', 'port', 25))
417 ui.note(_('sending mail: smtp host %s, port %s\n') %
420 ui.note(_('sending mail: smtp host %s, port %s\n') %
418 (mailhost, mailport))
421 (mailhost, mailport))
419 s.connect(host=mailhost, port=mailport)
422 s.connect(host=mailhost, port=mailport)
420 if ui.configbool('smtp', 'tls'):
423 if ui.configbool('smtp', 'tls'):
421 if not hasattr(socket, 'ssl'):
424 if not hasattr(socket, 'ssl'):
422 raise util.Abort(_("can't use TLS: Python SSL support "
425 raise util.Abort(_("can't use TLS: Python SSL support "
423 "not installed"))
426 "not installed"))
424 ui.note(_('(using tls)\n'))
427 ui.note(_('(using tls)\n'))
425 s.ehlo()
428 s.ehlo()
426 s.starttls()
429 s.starttls()
427 s.ehlo()
430 s.ehlo()
428
431
429 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
432 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
430 stored = password = password_store.get_smtp_password(
433 stored = password = password_store.get_smtp_password(
431 mailhost, mailport, username)
434 mailhost, mailport, username)
432 # No need to check whether password was found as try_smtp_login
435 # No need to check whether password was found as try_smtp_login
433 # just returns False if it is absent.
436 # just returns False if it is absent.
434 while not try_smtp_login(ui, s, username, password):
437 while not try_smtp_login(ui, s, username, password):
435 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
438 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
436
439
437 if stored != password:
440 if stored != password:
438 password_store.set_smtp_password(
441 password_store.set_smtp_password(
439 mailhost, mailport, username, password)
442 mailhost, mailport, username, password)
440 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
443 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
441
444
442 def send(sender, recipients, msg):
445 def send(sender, recipients, msg):
443 try:
446 try:
444 return s.sendmail(sender, recipients, msg)
447 return s.sendmail(sender, recipients, msg)
445 except smtplib.SMTPRecipientsRefused, inst:
448 except smtplib.SMTPRecipientsRefused, inst:
446 recipients = [r[1] for r in inst.recipients.values()]
449 recipients = [r[1] for r in inst.recipients.values()]
447 raise util.Abort('\n' + '\n'.join(recipients))
450 raise util.Abort('\n' + '\n'.join(recipients))
448 except smtplib.SMTPException, inst:
451 except smtplib.SMTPException, inst:
449 raise util.Abort(inst)
452 raise util.Abort(inst)
450
453
451 return send
454 return send
452
455
453 ############################################################
456 ############################################################
454
457
455 orig_smtp = mail._smtp
458 orig_smtp = mail._smtp
456
459
457 @monkeypatch_method(mail)
460 @monkeypatch_method(mail)
458 def _smtp(ui):
461 def _smtp(ui):
459 """
462 """
460 build an smtp connection and return a function to send email
463 build an smtp connection and return a function to send email
461
464
462 This is the monkeypatched version of _smtp(ui) function from
465 This is the monkeypatched version of _smtp(ui) function from
463 mercurial/mail.py. It calls the original unless username
466 mercurial/mail.py. It calls the original unless username
464 without password is given in the configuration.
467 without password is given in the configuration.
465 """
468 """
466 username = ui.config('smtp', 'username')
469 username = ui.config('smtp', 'username')
467 password = ui.config('smtp', 'password')
470 password = ui.config('smtp', 'password')
468
471
469 if username and not password:
472 if username and not password:
470 return keyring_supported_smtp(ui, username)
473 return keyring_supported_smtp(ui, username)
471 else:
474 else:
472 return orig_smtp(ui)
475 return orig_smtp(ui)
General Comments 0
You need to be logged in to leave comments. Login now