##// END OF EJS Templates
4.6 fix (one of…)
Marcin Kasperski -
r250:a9f4dc6c default
parent child Browse files
Show More
@@ -1,311 +1,317 b''
1
2
3 ~~~~~~~~~~~~
4
5 4.6-compatibility
6
1 1.1.8
7 1.1.8
2 ~~~~~~~~~~~~~
8 ~~~~~~~~~~~~~
3
9
4 Updated links after bitbucket changes.
10 Updated links after bitbucket changes.
5
11
6 1.1.7
12 1.1.7
7 ~~~~~~~~~~~~~~~~~~
13 ~~~~~~~~~~~~~~~~~~
8
14
9 #52 hg keyring_check and hg keyring_clear did not work since
15 #52 hg keyring_check and hg keyring_clear did not work since
10 Mercurial 3.9 (incompatibility caused by commit 2c019aac6b99,
16 Mercurial 3.9 (incompatibility caused by commit 2c019aac6b99,
11 introducing passwdb).
17 introducing passwdb).
12
18
13 1.1.6
19 1.1.6
14 ~~~~~~~~~~~~~~~~~~
20 ~~~~~~~~~~~~~~~~~~
15
21
16 Fixed NameError showing up in some password saving scenarios, in
22 Fixed NameError showing up in some password saving scenarios, in
17 particular in case of password save failures (thanks to Andrew
23 particular in case of password save failures (thanks to Andrew
18 Taumoefolau for reporting and fixing).
24 Taumoefolau for reporting and fixing).
19
25
20 1.1.5
26 1.1.5
21 ~~~~~~~~~~~~~~~~~~
27 ~~~~~~~~~~~~~~~~~~
22
28
23 Mercurial 3.9 compatibility.
29 Mercurial 3.9 compatibility.
24
30
25 1.1.4
31 1.1.4
26 ~~~~~~~~~~~~~~~~~~
32 ~~~~~~~~~~~~~~~~~~
27
33
28 Gracefully handle failures to save passwords - they are reported
34 Gracefully handle failures to save passwords - they are reported
29 as warnings, but don't break the operation being executed.
35 as warnings, but don't break the operation being executed.
30
36
31 Compatibility fixes for upcoming 3.9 release (which changes SSL API
37 Compatibility fixes for upcoming 3.9 release (which changes SSL API
32 noticeably, what impact SMTP passwords handling in mercurial_keyring).
38 noticeably, what impact SMTP passwords handling in mercurial_keyring).
33
39
34 1.1.3
40 1.1.3
35 ~~~~~~~~~~~~~~~~~~
41 ~~~~~~~~~~~~~~~~~~
36
42
37 Mercurial 3.8 compatibility for email over SSL/TLS (SMTPS/STARTTLS
43 Mercurial 3.8 compatibility for email over SSL/TLS (SMTPS/STARTTLS
38 constructors changed). Should not spoil older versions.
44 constructors changed). Should not spoil older versions.
39
45
40 1.1.2
46 1.1.2
41 ~~~~~~~~~~~~~~~~~~
47 ~~~~~~~~~~~~~~~~~~
42
48
43 The keyring_check and keyring_clear commands can be run outside
49 The keyring_check and keyring_clear commands can be run outside
44 repository (if given some path as parameter).
50 repository (if given some path as parameter).
45
51
46 Fixed some messages.
52 Fixed some messages.
47
53
48 README updates (a few language fixes, added note about GUI tools).
54 README updates (a few language fixes, added note about GUI tools).
49
55
50 1.1.1
56 1.1.1
51 ~~~~~~~~~~~~~~~~~~
57 ~~~~~~~~~~~~~~~~~~
52
58
53 #49 Fixed the bug due to url-stored usernames did not work (introduced
59 #49 Fixed the bug due to url-stored usernames did not work (introduced
54 in 1.0.0 and not completely fixed in 1.0.1).
60 in 1.0.0 and not completely fixed in 1.0.1).
55
61
56 #50 Bad doc url in error message
62 #50 Bad doc url in error message
57
63
58
64
59 1.1.0
65 1.1.0
60 ~~~~~~~~~~~~~~~~~~
66 ~~~~~~~~~~~~~~~~~~
61
67
62 Forward compatibility for Mercurial 3.8 (should not break old mercurials)
68 Forward compatibility for Mercurial 3.8 (should not break old mercurials)
63
69
64 1.0.1
70 1.0.1
65 ~~~~~~~~~~~~~~~~~~
71 ~~~~~~~~~~~~~~~~~~
66
72
67 URLs containing usernames (https://John@some.service/somewhat) were
73 URLs containing usernames (https://John@some.service/somewhat) were
68 not working unless username was also configured separately (username
74 not working unless username was also configured separately (username
69 presence in url was not detected properly).
75 presence in url was not detected properly).
70
76
71 Liberated prefix matching, path https://John@some.service/somewhat can
77 Liberated prefix matching, path https://John@some.service/somewhat can
72 be matched both against prefix https://some.service and against
78 be matched both against prefix https://some.service and against
73 https://John@some.service. That mostly matches what mercurial itself
79 https://John@some.service. That mostly matches what mercurial itself
74 does.
80 does.
75
81
76 1.0.0
82 1.0.0
77 ~~~~~~~~~~~~~~~~~~
83 ~~~~~~~~~~~~~~~~~~
78
84
79 Added
85 Added
80 hg keyring_check
86 hg keyring_check
81 and
87 and
82 hg keyring_clear PATH-OR-ALIAS
88 hg keyring_clear PATH-OR-ALIAS
83 commands
89 commands
84
90
85 Removed obsolete workarounds (compatibility for very old Mercurials -
91 Removed obsolete workarounds (compatibility for very old Mercurials -
86 some for pre-1.0, some for 1.4, some for 1.8/1.9).
92 some for pre-1.0, some for 1.4, some for 1.8/1.9).
87 Mercurial 2.0 is now required.
93 Mercurial 2.0 is now required.
88
94
89 Improved information about path prefix. In particular it is shown
95 Improved information about path prefix. In particular it is shown
90 whenever user is asked for password, for example:
96 whenever user is asked for password, for example:
91 hg pull bitbucket
97 hg pull bitbucket
92 http authorization required
98 http authorization required
93 realm: BitBucket
99 realm: BitBucket
94 url: https://bitbucket.org/Mekk
100 url: https://bitbucket.org/Mekk
95 user: Mekk (fixed in hgrc or url)
101 user: Mekk (fixed in hgrc or url)
96 password:
102 password:
97
103
98 Improved README.
104 Improved README.
99
105
100 Improved debug information.
106 Improved debug information.
101
107
102 0.8.0
108 0.8.0
103 ~~~~~~~~~~~~~~~~~~
109 ~~~~~~~~~~~~~~~~~~
104
110
105 Module is simplified a bit, but requires mercurial_extension_utils.
111 Module is simplified a bit, but requires mercurial_extension_utils.
106 Debug messages are prefixed with keyring: not [HgKeyring]
112 Debug messages are prefixed with keyring: not [HgKeyring]
107
113
108 0.7.1
114 0.7.1
109 ~~~~~~~~~~~~~~~~~~
115 ~~~~~~~~~~~~~~~~~~
110
116
111 #48 NullHandler import failure no longer breaks the extension.
117 #48 NullHandler import failure no longer breaks the extension.
112 May help python 2.6 compatibility.
118 May help python 2.6 compatibility.
113
119
114 0.7.0
120 0.7.0
115 ~~~~~~~~~~~~~~~~~~~
121 ~~~~~~~~~~~~~~~~~~~
116
122
117 Delaying keyring module import until passwords are really needed. It
123 Delaying keyring module import until passwords are really needed. It
118 can noticeably improve Mercurial (non pull/push) performance in some
124 can noticeably improve Mercurial (non pull/push) performance in some
119 cases (no longer slow hg status because D-Bus is busy an keyring tries
125 cases (no longer slow hg status because D-Bus is busy an keyring tries
120 to activate KDE Wallet through it…).
126 to activate KDE Wallet through it…).
121
127
122 0.6.7
128 0.6.7
123 ~~~~~~~~~~~~~~~~~
129 ~~~~~~~~~~~~~~~~~
124
130
125 #46 Fixed syntax of smtp.tls configuration setting (current Mercurials
131 #46 Fixed syntax of smtp.tls configuration setting (current Mercurials
126 doesn't handle "true" anymore, TortoiseHG crashed with mercurial
132 doesn't handle "true" anymore, TortoiseHG crashed with mercurial
127 keyring enabled while currently recommended starttls/smtps/none values
133 keyring enabled while currently recommended starttls/smtps/none values
128 were in use).
134 were in use).
129
135
130 0.6.6
136 0.6.6
131 ~~~~~~~~~~~~~~~~~
137 ~~~~~~~~~~~~~~~~~
132
138
133 #44 Handling some more mercurial versions in demandimport-detection
139 #44 Handling some more mercurial versions in demandimport-detection
134 logic.
140 logic.
135
141
136 0.6.5
142 0.6.5
137 ~~~~~~~~~~~~~~~~~
143 ~~~~~~~~~~~~~~~~~
138
144
139 #36 Shutting up warning about no logging handlers.
145 #36 Shutting up warning about no logging handlers.
140
146
141 0.6.4
147 0.6.4
142 ~~~~~~~~~~~~~~~~~
148 ~~~~~~~~~~~~~~~~~
143
149
144 #44 Pre-2.9.1 Mercurials compatibility (probing for active
150 #44 Pre-2.9.1 Mercurials compatibility (probing for active
145 demandimport differently).
151 demandimport differently).
146
152
147 0.6.3
153 0.6.3
148 ~~~~~~~~~~~~~~~~~
154 ~~~~~~~~~~~~~~~~~
149
155
150 #41 Fix for incorrect demandimport activity check logic, which could
156 #41 Fix for incorrect demandimport activity check logic, which could
151 cause various problems with imports after mercurial_keyring is
157 cause various problems with imports after mercurial_keyring is
152 imported.
158 imported.
153
159
154 0.6.2
160 0.6.2
155 ~~~~~~~~~~~~~~~~~
161 ~~~~~~~~~~~~~~~~~
156
162
157 #33 Fix for UnicodeDecodeErrors happening on some backends (especially
163 #33 Fix for UnicodeDecodeErrors happening on some backends (especially
158 Vault) when passwords with non-ascii characters are in use and native
164 Vault) when passwords with non-ascii characters are in use and native
159 locale is not utf-8. Passwords are no longer saved to keyring backends
165 locale is not utf-8. Passwords are no longer saved to keyring backends
160 as-entered, they are now decoded from local encoding (whichever is
166 as-entered, they are now decoded from local encoding (whichever is
161 detected by Mercurial), then encoded to unicode.
167 detected by Mercurial), then encoded to unicode.
162
168
163 0.6.1
169 0.6.1
164 ~~~~~~~~~~~~~~~~~
170 ~~~~~~~~~~~~~~~~~
165
171
166 #30 Yet another demandimport conflict fixed.
172 #30 Yet another demandimport conflict fixed.
167
173
168 0.6.0
174 0.6.0
169 ~~~~~~~~~~~~~~~~~
175 ~~~~~~~~~~~~~~~~~
170
176
171 #28 Disable demandimport completely during keyring import. Mayhaps it
177 #28 Disable demandimport completely during keyring import. Mayhaps it
172 will resolve (most) demandimport conflict errors.
178 will resolve (most) demandimport conflict errors.
173
179
174 0.5.7
180 0.5.7
175 ~~~~~~~~~~~~~~~~~
181 ~~~~~~~~~~~~~~~~~
176
182
177 #27 Some more demandimport ignores.
183 #27 Some more demandimport ignores.
178
184
179 0.5.6
185 0.5.6
180 ~~~~~~~~~~~~~~~~~
186 ~~~~~~~~~~~~~~~~~
181
187
182 #24, #25 Demandimport fixes (import failures in specific cases).
188 #24, #25 Demandimport fixes (import failures in specific cases).
183
189
184 Better way of demandimport-ignoring modules. In particular, we append
190 Better way of demandimport-ignoring modules. In particular, we append
185 more of them if gobject happens to be on the list.
191 more of them if gobject happens to be on the list.
186
192
187 0.5.5
193 0.5.5
188 ~~~~~~~~~~~~~~~~~
194 ~~~~~~~~~~~~~~~~~
189
195
190 Fix for gnome keyring import problems.
196 Fix for gnome keyring import problems.
191
197
192 0.5.4
198 0.5.4
193 ~~~~~~~~~~~~~~~~~
199 ~~~~~~~~~~~~~~~~~
194
200
195 #22 Some more demandimport ignores (fix import failures).
201 #22 Some more demandimport ignores (fix import failures).
196
202
197 SMTP password was not cleared properly (after detecting that it is
203 SMTP password was not cleared properly (after detecting that it is
198 invalid).
204 invalid).
199
205
200 Clarified license to be modified BSD style license.
206 Clarified license to be modified BSD style license.
201
207
202 0.5.3
208 0.5.3
203 ~~~~~~~~~~~~~~~~~
209 ~~~~~~~~~~~~~~~~~
204
210
205 Remove useless import which caused problems on Mercurial 2.3 when
211 Remove useless import which caused problems on Mercurial 2.3 when
206 demandimport was not enabled
212 demandimport was not enabled
207
213
208 0.5.1
214 0.5.1
209 ~~~~~~~~~~~~~~~~~
215 ~~~~~~~~~~~~~~~~~
210
216
211 Add help text to output for hg help.
217 Add help text to output for hg help.
212
218
213 0.5.0
219 0.5.0
214 ~~~~~~~~~~~~~~~~~
220 ~~~~~~~~~~~~~~~~~
215
221
216 Improved bad password detection. Internally: extension is now able to
222 Improved bad password detection. Internally: extension is now able to
217 properly differentiate between an authentication failure and a new
223 properly differentiate between an authentication failure and a new
218 request to the same url.
224 request to the same url.
219
225
220 Fixes in debug message
226 Fixes in debug message
221
227
222 Further debug messages patching
228 Further debug messages patching
223
229
224 Improving debug messages handling.
230 Improving debug messages handling.
225
231
226 Mercurial Keyring debug messages are now prefixed with
232 Mercurial Keyring debug messages are now prefixed with
227 [HgKeyring] to make distinguishing them easier
233 [HgKeyring] to make distinguishing them easier
228
234
229 0.4.6
235 0.4.6
230 ~~~~~~~~~~~~~~~~~
236 ~~~~~~~~~~~~~~~~~
231
237
232 More compatibility (changed signature of httpconnection.readauthforuri
238 More compatibility (changed signature of httpconnection.readauthforuri
233 , introduced post Mercurial 1.9 - since hg.0593e8f81c71)
239 , introduced post Mercurial 1.9 - since hg.0593e8f81c71)
234
240
235 Fix compatibility code which did not work due to demandimport issues
241 Fix compatibility code which did not work due to demandimport issues
236 (attempts to catch ImportErrors on "from mercurial.url import
242 (attempts to catch ImportErrors on "from mercurial.url import
237 readauthforuri" were not working properly).
243 readauthforuri" were not working properly).
238
244
239 0.4.5
245 0.4.5
240 ~~~~~~~~~~~~~~~~~
246 ~~~~~~~~~~~~~~~~~
241
247
242 Mercurial 1.9 compatibility (readauthforuri has been moved into new
248 Mercurial 1.9 compatibility (readauthforuri has been moved into new
243 httpconnection module).
249 httpconnection module).
244
250
245 0.4.4
251 0.4.4
246 ~~~~~~~~~~~~~~~~~
252 ~~~~~~~~~~~~~~~~~
247
253
248 Mercurial 1.8 compatibility (passwordmgr.readauthtoken() has been
254 Mercurial 1.8 compatibility (passwordmgr.readauthtoken() has been
249 moved into mercurial.url.readauthforuri).
255 moved into mercurial.url.readauthforuri).
250
256
251 0.4.3
257 0.4.3
252 ~~~~~~~~~~~~~~~~~
258 ~~~~~~~~~~~~~~~~~
253
259
254 Keyring fork no longer is needed as keyring releases are available
260 Keyring fork no longer is needed as keyring releases are available
255 again.
261 again.
256
262
257 Workaround for gnomekeyring mercurial.demandimport incompatibility:
263 Workaround for gnomekeyring mercurial.demandimport incompatibility:
258 mercurial.demandimport, which is enabled while in a mercurial
264 mercurial.demandimport, which is enabled while in a mercurial
259 extensions, prevents the correct import of gobject._gobject and
265 extensions, prevents the correct import of gobject._gobject and
260 consequently doesn't allow the loading of the gnomekeyring module,
266 consequently doesn't allow the loading of the gnomekeyring module,
261 which can be used by keyring. This just adds the proper module to
267 which can be used by keyring. This just adds the proper module to
262 demandimport ignore list.
268 demandimport ignore list.
263
269
264 0.4.2
270 0.4.2
265 ~~~~~~~~~~~~~~~~~
271 ~~~~~~~~~~~~~~~~~
266
272
267 No longer raising an error when username is specified both in ~/.hgrc
273 No longer raising an error when username is specified both in ~/.hgrc
268 and <repo>/.hg/hgrc if it is the same in both places.
274 and <repo>/.hg/hgrc if it is the same in both places.
269
275
270 Docs recommend sborho keyring fork.
276 Docs recommend sborho keyring fork.
271
277
272 0.4.1
278 0.4.1
273 ~~~~~~~~~~~~~~~~~
279 ~~~~~~~~~~~~~~~~~
274
280
275 Some tweaks and docs related to prefix handling.
281 Some tweaks and docs related to prefix handling.
276
282
277 Explicit information that keyring is not used due to lack of username.
283 Explicit information that keyring is not used due to lack of username.
278
284
279 0.4.0
285 0.4.0
280 ~~~~~~~~~~~~~~~~~
286 ~~~~~~~~~~~~~~~~~
281
287
282 Store and lookup prefix from [auth] so that password is shared amongst
288 Store and lookup prefix from [auth] so that password is shared amongst
283 shared auth entries
289 shared auth entries
284
290
285 0.3.3
291 0.3.3
286 ~~~~~~~~~~~~~~~~~
292 ~~~~~~~~~~~~~~~~~
287
293
288 Better error message
294 Better error message
289
295
290 0.3.2
296 0.3.2
291 ~~~~~~~~~~~~~~~~~
297 ~~~~~~~~~~~~~~~~~
292
298
293 Doc tweaks
299 Doc tweaks
294
300
295 0.3.1
301 0.3.1
296 ~~~~~~~~~~~~~~~~~
302 ~~~~~~~~~~~~~~~~~
297
303
298 Introduced and documented PyPi package, added setup.py
304 Introduced and documented PyPi package, added setup.py
299
305
300 0.2.0
306 0.2.0
301 ~~~~~~~~~~~~~~~~~
307 ~~~~~~~~~~~~~~~~~
302
308
303 Added handling of SMTP passwords (tested on patchbomb extension but
309 Added handling of SMTP passwords (tested on patchbomb extension but
304 should work on anything what utilizes mercurial.mail)
310 should work on anything what utilizes mercurial.mail)
305
311
306 Docstrings mention Debian keyring packages.
312 Docstrings mention Debian keyring packages.
307
313
308 0.1.1
314 0.1.1
309 ~~~~~~~~~~~~~~~~~
315 ~~~~~~~~~~~~~~~~~
310
316
311 Initial public release
317 Initial public release
@@ -1,830 +1,830 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 passwords to encrypted storage
32 '''securely save HTTP and SMTP passwords to encrypted storage
33
33
34 mercurial_keyring securely saves HTTP and SMTP passwords in password
34 mercurial_keyring securely saves HTTP and SMTP passwords in password
35 databases (Gnome Keyring, KDE KWallet, OSXKeyChain, Win32 crypto
35 databases (Gnome Keyring, KDE KWallet, OSXKeyChain, Win32 crypto
36 services).
36 services).
37
37
38 The process is automatic. Whenever bare Mercurial just prompts for
38 The process is automatic. Whenever bare Mercurial just prompts for
39 the password, Mercurial with mercurial_keyring enabled checks whether
39 the password, Mercurial with mercurial_keyring enabled checks whether
40 saved password is available first. If so, it is used. If not, you
40 saved password is available first. If so, it is used. If not, you
41 will be prompted for the password, but entered password will be
41 will be prompted for the password, but entered password will be
42 saved for the future use.
42 saved for the future use.
43
43
44 In case saved password turns out to be invalid (HTTP or SMTP login
44 In case saved password turns out to be invalid (HTTP or SMTP login
45 fails) it is dropped, and you are asked for current password.
45 fails) it is dropped, and you are asked for current password.
46
46
47 Actual password storage is implemented by Python keyring library, this
47 Actual password storage is implemented by Python keyring library, this
48 extension glues those services to Mercurial. Consult keyring
48 extension glues those services to Mercurial. Consult keyring
49 documentation for information how to configure actual password
49 documentation for information how to configure actual password
50 backend (by default keyring guesses, usually correctly, for example
50 backend (by default keyring guesses, usually correctly, for example
51 you get KDE Wallet under KDE, and Gnome Keyring under Gnome or Unity).
51 you get KDE Wallet under KDE, and Gnome Keyring under Gnome or Unity).
52 '''
52 '''
53
53
54 import urllib2
54 import urllib2
55 import smtplib
55 import smtplib
56 import socket
56 import socket
57 import os
57 import os
58 import sys
58 import sys
59 import re
59 import re
60
60
61 from mercurial import util, sslutil
61 from mercurial import util, sslutil, error
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.url import passwordmgr
63 from mercurial.url import passwordmgr
64 from mercurial import mail
64 from mercurial import mail
65 from mercurial.mail import SMTPS, STARTTLS
65 from mercurial.mail import SMTPS, STARTTLS
66 from mercurial import encoding
66 from mercurial import encoding
67
67
68 # pylint: disable=invalid-name, line-too-long, protected-access, too-many-arguments
68 # pylint: disable=invalid-name, line-too-long, protected-access, too-many-arguments
69
69
70 ###########################################################################
70 ###########################################################################
71 # Specific import trickery
71 # Specific import trickery
72 ###########################################################################
72 ###########################################################################
73
73
74
74
75 def import_meu():
75 def import_meu():
76 """
76 """
77 Convoluted import of mercurial_extension_utils, which helps
77 Convoluted import of mercurial_extension_utils, which helps
78 TortoiseHg/Win setups. This routine and it's use below
78 TortoiseHg/Win setups. This routine and it's use below
79 performs equivalent of
79 performs equivalent of
80 from mercurial_extension_utils import monkeypatch_method
80 from mercurial_extension_utils import monkeypatch_method
81 but looks for some non-path directories.
81 but looks for some non-path directories.
82 """
82 """
83 try:
83 try:
84 import mercurial_extension_utils
84 import mercurial_extension_utils
85 except ImportError:
85 except ImportError:
86 my_dir = os.path.dirname(__file__)
86 my_dir = os.path.dirname(__file__)
87 sys.path.extend([
87 sys.path.extend([
88 # In the same dir (manual or site-packages after pip)
88 # In the same dir (manual or site-packages after pip)
89 my_dir,
89 my_dir,
90 # Developer clone
90 # Developer clone
91 os.path.join(os.path.dirname(my_dir), "extension_utils"),
91 os.path.join(os.path.dirname(my_dir), "extension_utils"),
92 # Side clone
92 # Side clone
93 os.path.join(os.path.dirname(my_dir), "mercurial-extension_utils"),
93 os.path.join(os.path.dirname(my_dir), "mercurial-extension_utils"),
94 ])
94 ])
95 try:
95 try:
96 import mercurial_extension_utils
96 import mercurial_extension_utils
97 except ImportError:
97 except ImportError:
98 raise util.Abort(_("""Can not import mercurial_extension_utils.
98 raise error.Abort(_("""Can not import mercurial_extension_utils.
99 Please install this module in Python path.
99 Please install this module in Python path.
100 See Installation chapter in https://bitbucket.org/Mekk/mercurial_keyring/ for details
100 See Installation chapter in https://bitbucket.org/Mekk/mercurial_keyring/ for details
101 (and for info about TortoiseHG on Windows, or other bundled Python)."""))
101 (and for info about TortoiseHG on Windows, or other bundled Python)."""))
102 return mercurial_extension_utils
102 return mercurial_extension_utils
103
103
104 meu = import_meu()
104 meu = import_meu()
105 monkeypatch_method = meu.monkeypatch_method
105 monkeypatch_method = meu.monkeypatch_method
106
106
107
107
108 def import_keyring():
108 def import_keyring():
109 """
109 """
110 Importing keyring happens to be costly if wallet is slow, so we delay it
110 Importing keyring happens to be costly if wallet is slow, so we delay it
111 until it is really needed. The routine below also works around various
111 until it is really needed. The routine below also works around various
112 demandimport-related problems.
112 demandimport-related problems.
113 """
113 """
114 # mercurial.demandimport incompatibility workaround.
114 # mercurial.demandimport incompatibility workaround.
115 # various keyring backends fail as they can't properly import helper
115 # various keyring backends fail as they can't properly import helper
116 # modules (as demandimport modifies python import behaviour).
116 # modules (as demandimport modifies python import behaviour).
117 # If you get import errors with demandimport in backtrace, try
117 # If you get import errors with demandimport in backtrace, try
118 # guessing what to block and extending the list below.
118 # guessing what to block and extending the list below.
119 mod, was_imported_now = meu.direct_import_ext(
119 mod, was_imported_now = meu.direct_import_ext(
120 "keyring", [
120 "keyring", [
121 "gobject._gobject",
121 "gobject._gobject",
122 "configparser",
122 "configparser",
123 "json",
123 "json",
124 "abc",
124 "abc",
125 "io",
125 "io",
126 "keyring",
126 "keyring",
127 "gdata.docs.service",
127 "gdata.docs.service",
128 "gdata.service",
128 "gdata.service",
129 "types",
129 "types",
130 "atom.http",
130 "atom.http",
131 "atom.http_interface",
131 "atom.http_interface",
132 "atom.service",
132 "atom.service",
133 "atom.token_store",
133 "atom.token_store",
134 "ctypes",
134 "ctypes",
135 "secretstorage.exceptions",
135 "secretstorage.exceptions",
136 "fs.opener",
136 "fs.opener",
137 ])
137 ])
138 if was_imported_now:
138 if was_imported_now:
139 # Shut up warning about uninitialized logging by keyring
139 # Shut up warning about uninitialized logging by keyring
140 meu.disable_logging("keyring")
140 meu.disable_logging("keyring")
141 return mod
141 return mod
142
142
143 #################################################################
143 #################################################################
144 # Actual implementation
144 # Actual implementation
145 #################################################################
145 #################################################################
146
146
147 KEYRING_SERVICE = "Mercurial"
147 KEYRING_SERVICE = "Mercurial"
148
148
149
149
150 class PasswordStore(object):
150 class PasswordStore(object):
151 """
151 """
152 Helper object handling keyring usage (password save&restore,
152 Helper object handling keyring usage (password save&restore,
153 the way passwords are keyed in the keyring).
153 the way passwords are keyed in the keyring).
154 """
154 """
155 def __init__(self):
155 def __init__(self):
156 self.cache = dict()
156 self.cache = dict()
157
157
158 def get_http_password(self, url, username):
158 def get_http_password(self, url, username):
159 """
159 """
160 Checks whether password of username for url is available,
160 Checks whether password of username for url is available,
161 returns it or None
161 returns it or None
162 """
162 """
163 return self._read_password_from_keyring(
163 return self._read_password_from_keyring(
164 self._format_http_key(url, username))
164 self._format_http_key(url, username))
165
165
166 def set_http_password(self, url, username, password):
166 def set_http_password(self, url, username, password):
167 """Saves password to keyring"""
167 """Saves password to keyring"""
168 self._save_password_to_keyring(
168 self._save_password_to_keyring(
169 self._format_http_key(url, username),
169 self._format_http_key(url, username),
170 password)
170 password)
171
171
172 def clear_http_password(self, url, username):
172 def clear_http_password(self, url, username):
173 """Drops saved password"""
173 """Drops saved password"""
174 self.set_http_password(url, username, "")
174 self.set_http_password(url, username, "")
175
175
176 @staticmethod
176 @staticmethod
177 def _format_http_key(url, username):
177 def _format_http_key(url, username):
178 """Construct actual key for password identification"""
178 """Construct actual key for password identification"""
179 return "%s@@%s" % (username, url)
179 return "%s@@%s" % (username, url)
180
180
181 def get_smtp_password(self, machine, port, username):
181 def get_smtp_password(self, machine, port, username):
182 """Checks for SMTP password in keyring, returns
182 """Checks for SMTP password in keyring, returns
183 password or None"""
183 password or None"""
184 return self._read_password_from_keyring(
184 return self._read_password_from_keyring(
185 self._format_smtp_key(machine, port, username))
185 self._format_smtp_key(machine, port, username))
186
186
187 def set_smtp_password(self, machine, port, username, password):
187 def set_smtp_password(self, machine, port, username, password):
188 """Saves SMTP password to keyring"""
188 """Saves SMTP password to keyring"""
189 self._save_password_to_keyring(
189 self._save_password_to_keyring(
190 self._format_smtp_key(machine, port, username),
190 self._format_smtp_key(machine, port, username),
191 password)
191 password)
192
192
193 def clear_smtp_password(self, machine, port, username):
193 def clear_smtp_password(self, machine, port, username):
194 """Drops saved SMTP password"""
194 """Drops saved SMTP password"""
195 self.set_smtp_password(machine, port, username, "")
195 self.set_smtp_password(machine, port, username, "")
196
196
197 @staticmethod
197 @staticmethod
198 def _format_smtp_key(machine, port, username):
198 def _format_smtp_key(machine, port, username):
199 """Construct key for SMTP password identification"""
199 """Construct key for SMTP password identification"""
200 return "%s@@%s:%s" % (username, machine, str(port))
200 return "%s@@%s:%s" % (username, machine, str(port))
201
201
202 @staticmethod
202 @staticmethod
203 def _read_password_from_keyring(pwdkey):
203 def _read_password_from_keyring(pwdkey):
204 """Physically read from keyring"""
204 """Physically read from keyring"""
205 keyring = import_keyring()
205 keyring = import_keyring()
206 password = keyring.get_password(KEYRING_SERVICE, pwdkey)
206 password = keyring.get_password(KEYRING_SERVICE, pwdkey)
207 # Reverse recoding from next routine
207 # Reverse recoding from next routine
208 if isinstance(password, unicode):
208 if isinstance(password, unicode):
209 return encoding.tolocal(password.encode('utf-8'))
209 return encoding.tolocal(password.encode('utf-8'))
210 return password
210 return password
211
211
212 @staticmethod
212 @staticmethod
213 def _save_password_to_keyring(pwdkey, password):
213 def _save_password_to_keyring(pwdkey, password):
214 """Physically write to keyring"""
214 """Physically write to keyring"""
215 keyring = import_keyring()
215 keyring = import_keyring()
216 # keyring in general expects unicode.
216 # keyring in general expects unicode.
217 # Mercurial provides "local" encoding. See #33
217 # Mercurial provides "local" encoding. See #33
218 password = encoding.fromlocal(password).decode('utf-8')
218 password = encoding.fromlocal(password).decode('utf-8')
219 keyring.set_password(
219 keyring.set_password(
220 KEYRING_SERVICE, pwdkey, password)
220 KEYRING_SERVICE, pwdkey, password)
221
221
222 password_store = PasswordStore()
222 password_store = PasswordStore()
223
223
224
224
225 ############################################################
225 ############################################################
226 # Various utils
226 # Various utils
227 ############################################################
227 ############################################################
228
228
229 def _debug(ui, msg):
229 def _debug(ui, msg):
230 """Generic debug message"""
230 """Generic debug message"""
231 ui.debug("keyring: " + msg + "\n")
231 ui.debug("keyring: " + msg + "\n")
232
232
233
233
234 class PwdCache(object):
234 class PwdCache(object):
235 """Short term cache, used to preserve passwords
235 """Short term cache, used to preserve passwords
236 if they are used twice during a command"""
236 if they are used twice during a command"""
237 def __init__(self):
237 def __init__(self):
238 self._cache = {}
238 self._cache = {}
239
239
240 def store(self, realm, url, user, pwd):
240 def store(self, realm, url, user, pwd):
241 """Saves password"""
241 """Saves password"""
242 cache_key = (realm, url, user)
242 cache_key = (realm, url, user)
243 self._cache[cache_key] = pwd
243 self._cache[cache_key] = pwd
244
244
245 def check(self, realm, url, user):
245 def check(self, realm, url, user):
246 """Checks for cached password"""
246 """Checks for cached password"""
247 cache_key = (realm, url, user)
247 cache_key = (realm, url, user)
248 return self._cache.get(cache_key)
248 return self._cache.get(cache_key)
249
249
250
250
251 _re_http_url = re.compile(r'^https?://')
251 _re_http_url = re.compile(r'^https?://')
252
252
253 def is_http_path(url):
253 def is_http_path(url):
254 return bool(_re_http_url.search(url))
254 return bool(_re_http_url.search(url))
255
255
256
256
257 def make_passwordmgr(ui):
257 def make_passwordmgr(ui):
258 """Constructing passwordmgr in a way compatible with various mercurials"""
258 """Constructing passwordmgr in a way compatible with various mercurials"""
259 if hasattr(ui, 'httppasswordmgrdb'):
259 if hasattr(ui, 'httppasswordmgrdb'):
260 return passwordmgr(ui, ui.httppasswordmgrdb)
260 return passwordmgr(ui, ui.httppasswordmgrdb)
261 else:
261 else:
262 return passwordmgr(ui)
262 return passwordmgr(ui)
263
263
264 ############################################################
264 ############################################################
265 # HTTP password management
265 # HTTP password management
266 ############################################################
266 ############################################################
267
267
268
268
269 class HTTPPasswordHandler(object):
269 class HTTPPasswordHandler(object):
270 """
270 """
271 Actual implementation of password handling (user prompting,
271 Actual implementation of password handling (user prompting,
272 configuration file searching, keyring save&restore).
272 configuration file searching, keyring save&restore).
273
273
274 Object of this class is bound as passwordmgr attribute.
274 Object of this class is bound as passwordmgr attribute.
275 """
275 """
276 def __init__(self):
276 def __init__(self):
277 self.pwd_cache = PwdCache()
277 self.pwd_cache = PwdCache()
278 self.last_reply = None
278 self.last_reply = None
279
279
280 # Markers and also names used in debug notes. Password source
280 # Markers and also names used in debug notes. Password source
281 SRC_URL = "repository URL"
281 SRC_URL = "repository URL"
282 SRC_CFGAUTH = "hgrc"
282 SRC_CFGAUTH = "hgrc"
283 SRC_MEMCACHE = "temporary cache"
283 SRC_MEMCACHE = "temporary cache"
284 SRC_URLCACHE = "urllib temporary cache"
284 SRC_URLCACHE = "urllib temporary cache"
285 SRC_KEYRING = "keyring"
285 SRC_KEYRING = "keyring"
286
286
287 def get_credentials(self, pwmgr, realm, authuri, skip_caches=False):
287 def get_credentials(self, pwmgr, realm, authuri, skip_caches=False):
288 """
288 """
289 Looks up for user credentials in various places, returns them
289 Looks up for user credentials in various places, returns them
290 and information about their source.
290 and information about their source.
291
291
292 Used internally inside find_auth and inside informative
292 Used internally inside find_auth and inside informative
293 commands (thiis method doesn't cache, doesn't detect bad
293 commands (thiis method doesn't cache, doesn't detect bad
294 passwords etc, doesn't prompt interactively, doesn't store
294 passwords etc, doesn't prompt interactively, doesn't store
295 password in keyring).
295 password in keyring).
296
296
297 Returns: user, password, SRC_*, actual_url
297 Returns: user, password, SRC_*, actual_url
298
298
299 If not found, password and SRC is None, user can be given or
299 If not found, password and SRC is None, user can be given or
300 not, url is always set
300 not, url is always set
301 """
301 """
302 ui = pwmgr.ui
302 ui = pwmgr.ui
303
303
304 parsed_url, url_user, url_passwd = self.unpack_url(authuri)
304 parsed_url, url_user, url_passwd = self.unpack_url(authuri)
305 base_url = str(parsed_url)
305 base_url = str(parsed_url)
306 ui.debug(_('keyring: base url: %s, url user: %s, url pwd: %s\n') %
306 ui.debug(_('keyring: base url: %s, url user: %s, url pwd: %s\n') %
307 (base_url, url_user or '', url_passwd and '******' or ''))
307 (base_url, url_user or '', url_passwd and '******' or ''))
308
308
309 # Extract username (or password) stored directly in url
309 # Extract username (or password) stored directly in url
310 if url_user and url_passwd:
310 if url_user and url_passwd:
311 return url_user, url_passwd, self.SRC_URL, base_url
311 return url_user, url_passwd, self.SRC_URL, base_url
312
312
313 # Extract data from urllib (in case it was already stored)
313 # Extract data from urllib (in case it was already stored)
314 if isinstance(pwmgr, urllib2.HTTPPasswordMgrWithDefaultRealm):
314 if isinstance(pwmgr, urllib2.HTTPPasswordMgrWithDefaultRealm):
315 urllib_user, urllib_pwd = \
315 urllib_user, urllib_pwd = \
316 urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
316 urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
317 pwmgr, realm, authuri)
317 pwmgr, realm, authuri)
318 else:
318 else:
319 urllib_user, urllib_pwd = pwmgr.passwddb.find_user_password(
319 urllib_user, urllib_pwd = pwmgr.passwddb.find_user_password(
320 realm, authuri)
320 realm, authuri)
321 if urllib_user and urllib_pwd:
321 if urllib_user and urllib_pwd:
322 return urllib_user, urllib_pwd, self.SRC_URLCACHE, base_url
322 return urllib_user, urllib_pwd, self.SRC_URLCACHE, base_url
323
323
324 actual_user = url_user or urllib_user
324 actual_user = url_user or urllib_user
325
325
326 # Consult configuration to normalize url to prefix, and find username
326 # Consult configuration to normalize url to prefix, and find username
327 # (and maybe password)
327 # (and maybe password)
328 auth_user, auth_pwd, keyring_url = self.get_url_config(
328 auth_user, auth_pwd, keyring_url = self.get_url_config(
329 ui, parsed_url, actual_user)
329 ui, parsed_url, actual_user)
330 if auth_user and actual_user and (actual_user != auth_user):
330 if auth_user and actual_user and (actual_user != auth_user):
331 raise util.Abort(_('keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, actual_user, auth_user)))
331 raise error.Abort(_('keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, actual_user, auth_user)))
332 if auth_user and auth_pwd:
332 if auth_user and auth_pwd:
333 return auth_user, auth_pwd, self.SRC_CFGAUTH, keyring_url
333 return auth_user, auth_pwd, self.SRC_CFGAUTH, keyring_url
334
334
335 actual_user = actual_user or auth_user
335 actual_user = actual_user or auth_user
336
336
337 if skip_caches:
337 if skip_caches:
338 return actual_user, None, None, keyring_url
338 return actual_user, None, None, keyring_url
339
339
340 # Check memory cache (reuse )
340 # Check memory cache (reuse )
341 # Checking the memory cache (there may be many http calls per command)
341 # Checking the memory cache (there may be many http calls per command)
342 cached_pwd = self.pwd_cache.check(realm, keyring_url, actual_user)
342 cached_pwd = self.pwd_cache.check(realm, keyring_url, actual_user)
343 if cached_pwd:
343 if cached_pwd:
344 return actual_user, cached_pwd, self.SRC_MEMCACHE, keyring_url
344 return actual_user, cached_pwd, self.SRC_MEMCACHE, keyring_url
345
345
346 # Load from keyring.
346 # Load from keyring.
347 if actual_user:
347 if actual_user:
348 ui.debug(_("keyring: looking for password (user %s, url %s)\n") % (actual_user, keyring_url))
348 ui.debug(_("keyring: looking for password (user %s, url %s)\n") % (actual_user, keyring_url))
349 keyring_pwd = password_store.get_http_password(keyring_url, actual_user)
349 keyring_pwd = password_store.get_http_password(keyring_url, actual_user)
350 if keyring_pwd:
350 if keyring_pwd:
351 return actual_user, keyring_pwd, self.SRC_KEYRING, keyring_url
351 return actual_user, keyring_pwd, self.SRC_KEYRING, keyring_url
352
352
353 return actual_user, None, None, keyring_url
353 return actual_user, None, None, keyring_url
354
354
355 @staticmethod
355 @staticmethod
356 def prompt_interactively(ui, user, realm, url):
356 def prompt_interactively(ui, user, realm, url):
357 """Actual interactive prompt"""
357 """Actual interactive prompt"""
358 if not ui.interactive():
358 if not ui.interactive():
359 raise util.Abort(_('keyring: http authorization required but program used in non-interactive mode'))
359 raise error.Abort(_('keyring: http authorization required but program used in non-interactive mode'))
360
360
361 if not user:
361 if not user:
362 ui.status(_("keyring: username not specified in hgrc (or in url). Password will not be saved.\n"))
362 ui.status(_("keyring: username not specified in hgrc (or in url). Password will not be saved.\n"))
363
363
364 ui.write(_("http authorization required\n"))
364 ui.write(_("http authorization required\n"))
365 ui.status(_("realm: %s\n") % realm)
365 ui.status(_("realm: %s\n") % realm)
366 ui.status(_("url: %s\n") % url)
366 ui.status(_("url: %s\n") % url)
367 if user:
367 if user:
368 ui.write(_("user: %s (fixed in hgrc or url)\n" % user))
368 ui.write(_("user: %s (fixed in hgrc or url)\n" % user))
369 else:
369 else:
370 user = ui.prompt(_("user:"), default=None)
370 user = ui.prompt(_("user:"), default=None)
371 pwd = ui.getpass(_("password: "))
371 pwd = ui.getpass(_("password: "))
372 return user, pwd
372 return user, pwd
373
373
374 def find_auth(self, pwmgr, realm, authuri, req):
374 def find_auth(self, pwmgr, realm, authuri, req):
375 """
375 """
376 Actual implementation of find_user_password - different
376 Actual implementation of find_user_password - different
377 ways of obtaining the username and password.
377 ways of obtaining the username and password.
378
378
379 Returns pair username, password
379 Returns pair username, password
380 """
380 """
381 ui = pwmgr.ui
381 ui = pwmgr.ui
382 after_bad_auth = self._after_bad_auth(ui, realm, authuri, req)
382 after_bad_auth = self._after_bad_auth(ui, realm, authuri, req)
383
383
384 # Look in url, cache, etc
384 # Look in url, cache, etc
385 user, pwd, src, final_url = self.get_credentials(
385 user, pwd, src, final_url = self.get_credentials(
386 pwmgr, realm, authuri, skip_caches=after_bad_auth)
386 pwmgr, realm, authuri, skip_caches=after_bad_auth)
387 if pwd:
387 if pwd:
388 if src != self.SRC_MEMCACHE:
388 if src != self.SRC_MEMCACHE:
389 self.pwd_cache.store(realm, final_url, user, pwd)
389 self.pwd_cache.store(realm, final_url, user, pwd)
390 self._note_last_reply(realm, authuri, user, req)
390 self._note_last_reply(realm, authuri, user, req)
391 _debug(ui, _("Password found in " + src))
391 _debug(ui, _("Password found in " + src))
392 return user, pwd
392 return user, pwd
393
393
394 # Last resort: interactive prompt
394 # Last resort: interactive prompt
395 user, pwd = self.prompt_interactively(ui, user, realm, final_url)
395 user, pwd = self.prompt_interactively(ui, user, realm, final_url)
396
396
397 if user:
397 if user:
398 # Saving password to the keyring.
398 # Saving password to the keyring.
399 # It is done only if username is permanently set.
399 # It is done only if username is permanently set.
400 # Otherwise we won't be able to find the password so it
400 # Otherwise we won't be able to find the password so it
401 # does not make much sense to preserve it
401 # does not make much sense to preserve it
402 _debug(ui, _("Saving password for %s to keyring") % user)
402 _debug(ui, _("Saving password for %s to keyring") % user)
403 try:
403 try:
404 password_store.set_http_password(final_url, user, pwd)
404 password_store.set_http_password(final_url, user, pwd)
405 except Exception, e:
405 except Exception, e:
406 keyring = import_keyring()
406 keyring = import_keyring()
407 if isinstance(e, keyring.errors.PasswordSetError):
407 if isinstance(e, keyring.errors.PasswordSetError):
408 ui.traceback()
408 ui.traceback()
409 ui.warn(_("warning: failed to save password in keyring\n"))
409 ui.warn(_("warning: failed to save password in keyring\n"))
410 else:
410 else:
411 raise e
411 raise e
412
412
413 # Saving password to the memory cache
413 # Saving password to the memory cache
414 self.pwd_cache.store(realm, final_url, user, pwd)
414 self.pwd_cache.store(realm, final_url, user, pwd)
415 self._note_last_reply(realm, authuri, user, req)
415 self._note_last_reply(realm, authuri, user, req)
416 _debug(ui, _("Manually entered password"))
416 _debug(ui, _("Manually entered password"))
417 return user, pwd
417 return user, pwd
418
418
419 def get_url_config(self, ui, parsed_url, user):
419 def get_url_config(self, ui, parsed_url, user):
420 """
420 """
421 Checks configuration to decide whether/which username, prefix,
421 Checks configuration to decide whether/which username, prefix,
422 and password are configured for given url. Consults [auth] section.
422 and password are configured for given url. Consults [auth] section.
423
423
424 Returns tuple (username, password, prefix) containing elements
424 Returns tuple (username, password, prefix) containing elements
425 found. username and password can be None (if unset), if prefix
425 found. username and password can be None (if unset), if prefix
426 is not found, url itself is returned.
426 is not found, url itself is returned.
427 """
427 """
428 base_url = str(parsed_url)
428 base_url = str(parsed_url)
429
429
430 from mercurial.httpconnection import readauthforuri
430 from mercurial.httpconnection import readauthforuri
431 _debug(ui, _("Checking for hgrc info about url %s, user %s") % (base_url, user))
431 _debug(ui, _("Checking for hgrc info about url %s, user %s") % (base_url, user))
432 res = readauthforuri(ui, base_url, user)
432 res = readauthforuri(ui, base_url, user)
433 # If it user-less version not work, let's try with added username to handle
433 # If it user-less version not work, let's try with added username to handle
434 # both config conventions
434 # both config conventions
435 if (not res) and user:
435 if (not res) and user:
436 parsed_url.user = user
436 parsed_url.user = user
437 res = readauthforuri(ui, str(parsed_url), user)
437 res = readauthforuri(ui, str(parsed_url), user)
438 parsed_url.user = None
438 parsed_url.user = None
439 if res:
439 if res:
440 group, auth_token = res
440 group, auth_token = res
441 else:
441 else:
442 auth_token = None
442 auth_token = None
443
443
444 if auth_token:
444 if auth_token:
445 username = auth_token.get('username')
445 username = auth_token.get('username')
446 password = auth_token.get('password')
446 password = auth_token.get('password')
447 prefix = auth_token.get('prefix')
447 prefix = auth_token.get('prefix')
448 else:
448 else:
449 username = user
449 username = user
450 password = None
450 password = None
451 prefix = None
451 prefix = None
452
452
453 password_url = self.password_url(base_url, prefix)
453 password_url = self.password_url(base_url, prefix)
454
454
455 _debug(ui, _("Password url: %s, user: %s, password: %s (prefix: %s)") % (
455 _debug(ui, _("Password url: %s, user: %s, password: %s (prefix: %s)") % (
456 password_url, username, '********' if password else '', prefix))
456 password_url, username, '********' if password else '', prefix))
457
457
458 return username, password, password_url
458 return username, password, password_url
459
459
460 def _note_last_reply(self, realm, authuri, user, req):
460 def _note_last_reply(self, realm, authuri, user, req):
461 """
461 """
462 Internal helper. Saves info about auth-data obtained,
462 Internal helper. Saves info about auth-data obtained,
463 preserves them in last_reply, and returns pair user, pwd
463 preserves them in last_reply, and returns pair user, pwd
464 """
464 """
465 self.last_reply = dict(realm=realm, authuri=authuri,
465 self.last_reply = dict(realm=realm, authuri=authuri,
466 user=user, req=req)
466 user=user, req=req)
467
467
468 def _after_bad_auth(self, ui, realm, authuri, req):
468 def _after_bad_auth(self, ui, realm, authuri, req):
469 """
469 """
470 If we are called again just after identical previous
470 If we are called again just after identical previous
471 request, then the previously returned auth must have been
471 request, then the previously returned auth must have been
472 wrong. So we note this to force password prompt (and avoid
472 wrong. So we note this to force password prompt (and avoid
473 reusing bad password indefinitely).
473 reusing bad password indefinitely).
474
474
475 This routine checks for this condition.
475 This routine checks for this condition.
476 """
476 """
477 if self.last_reply:
477 if self.last_reply:
478 if (self.last_reply['realm'] == realm) \
478 if (self.last_reply['realm'] == realm) \
479 and (self.last_reply['authuri'] == authuri) \
479 and (self.last_reply['authuri'] == authuri) \
480 and (self.last_reply['req'] == req):
480 and (self.last_reply['req'] == req):
481 _debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
481 _debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
482 return True
482 return True
483 return False
483 return False
484
484
485 @staticmethod
485 @staticmethod
486 def password_url(base_url, prefix):
486 def password_url(base_url, prefix):
487 """Calculates actual url identifying the password. Takes
487 """Calculates actual url identifying the password. Takes
488 configured prefix under consideration (so can be shorter
488 configured prefix under consideration (so can be shorter
489 than repo url)"""
489 than repo url)"""
490 if not prefix or prefix == '*':
490 if not prefix or prefix == '*':
491 return base_url
491 return base_url
492 scheme, hostpath = base_url.split('://', 1)
492 scheme, hostpath = base_url.split('://', 1)
493 p = prefix.split('://', 1)
493 p = prefix.split('://', 1)
494 if len(p) > 1:
494 if len(p) > 1:
495 prefix_host_path = p[1]
495 prefix_host_path = p[1]
496 else:
496 else:
497 prefix_host_path = prefix
497 prefix_host_path = prefix
498 password_url = scheme + '://' + prefix_host_path
498 password_url = scheme + '://' + prefix_host_path
499 return password_url
499 return password_url
500
500
501 @staticmethod
501 @staticmethod
502 def unpack_url(authuri):
502 def unpack_url(authuri):
503 """
503 """
504 Takes original url for which authentication is attempted and:
504 Takes original url for which authentication is attempted and:
505
505
506 - Strips query params from url. Used to convert urls like
506 - Strips query params from url. Used to convert urls like
507 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
507 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
508 to
508 to
509 https://repo.machine.com/repos/apps/module
509 https://repo.machine.com/repos/apps/module
510
510
511 - Extracts username and password, if present, and removes them from url
511 - Extracts username and password, if present, and removes them from url
512 (so prefix matching works properly)
512 (so prefix matching works properly)
513
513
514 Returns url, user, password
514 Returns url, user, password
515 where url is mercurial.util.url object already stripped of all those
515 where url is mercurial.util.url object already stripped of all those
516 params.
516 params.
517 """
517 """
518 # mercurial.util.url, rather handy url parser
518 # mercurial.util.url, rather handy url parser
519 parsed_url = util.url(authuri)
519 parsed_url = util.url(authuri)
520 parsed_url.query = ''
520 parsed_url.query = ''
521 parsed_url.fragment = None
521 parsed_url.fragment = None
522 # Strip arguments to get actual remote repository url.
522 # Strip arguments to get actual remote repository url.
523 # base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
523 # base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
524 # parsed_url.path)
524 # parsed_url.path)
525 user = parsed_url.user
525 user = parsed_url.user
526 passwd = parsed_url.passwd
526 passwd = parsed_url.passwd
527 parsed_url.user = None
527 parsed_url.user = None
528 parsed_url.passwd = None
528 parsed_url.passwd = None
529
529
530 return parsed_url, user, passwd
530 return parsed_url, user, passwd
531
531
532
532
533 ############################################################
533 ############################################################
534 # Mercurial monkey-patching
534 # Mercurial monkey-patching
535 ############################################################
535 ############################################################
536
536
537
537
538 @monkeypatch_method(passwordmgr)
538 @monkeypatch_method(passwordmgr)
539 def find_user_password(self, realm, authuri):
539 def find_user_password(self, realm, authuri):
540 """
540 """
541 keyring-based implementation of username/password query
541 keyring-based implementation of username/password query
542 for HTTP(S) connections
542 for HTTP(S) connections
543
543
544 Passwords are saved in gnome keyring, OSX/Chain or other platform
544 Passwords are saved in gnome keyring, OSX/Chain or other platform
545 specific storage and keyed by the repository url
545 specific storage and keyed by the repository url
546 """
546 """
547 # Extend object attributes
547 # Extend object attributes
548 if not hasattr(self, '_pwd_handler'):
548 if not hasattr(self, '_pwd_handler'):
549 self._pwd_handler = HTTPPasswordHandler()
549 self._pwd_handler = HTTPPasswordHandler()
550
550
551 if hasattr(self, '_http_req'):
551 if hasattr(self, '_http_req'):
552 req = self._http_req
552 req = self._http_req
553 else:
553 else:
554 req = None
554 req = None
555
555
556 return self._pwd_handler.find_auth(self, realm, authuri, req)
556 return self._pwd_handler.find_auth(self, realm, authuri, req)
557
557
558
558
559 @monkeypatch_method(urllib2.AbstractBasicAuthHandler, "http_error_auth_reqed")
559 @monkeypatch_method(urllib2.AbstractBasicAuthHandler, "http_error_auth_reqed")
560 def basic_http_error_auth_reqed(self, authreq, host, req, headers):
560 def basic_http_error_auth_reqed(self, authreq, host, req, headers):
561 """Preserves current HTTP request so it can be consulted
561 """Preserves current HTTP request so it can be consulted
562 in find_user_password above"""
562 in find_user_password above"""
563 self.passwd._http_req = req
563 self.passwd._http_req = req
564 try:
564 try:
565 return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
565 return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
566 finally:
566 finally:
567 self.passwd._http_req = None
567 self.passwd._http_req = None
568
568
569
569
570 @monkeypatch_method(urllib2.AbstractDigestAuthHandler, "http_error_auth_reqed")
570 @monkeypatch_method(urllib2.AbstractDigestAuthHandler, "http_error_auth_reqed")
571 def digest_http_error_auth_reqed(self, authreq, host, req, headers):
571 def digest_http_error_auth_reqed(self, authreq, host, req, headers):
572 """Preserves current HTTP request so it can be consulted
572 """Preserves current HTTP request so it can be consulted
573 in find_user_password above"""
573 in find_user_password above"""
574 self.passwd._http_req = req
574 self.passwd._http_req = req
575 try:
575 try:
576 return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
576 return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
577 finally:
577 finally:
578 self.passwd._http_req = None
578 self.passwd._http_req = None
579
579
580 ############################################################
580 ############################################################
581 # SMTP support
581 # SMTP support
582 ############################################################
582 ############################################################
583
583
584
584
585 def try_smtp_login(ui, smtp_obj, username, password):
585 def try_smtp_login(ui, smtp_obj, username, password):
586 """
586 """
587 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
587 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
588 password.
588 password.
589
589
590 Returns:
590 Returns:
591 - True if login succeeded
591 - True if login succeeded
592 - False if login failed due to the wrong credentials
592 - False if login failed due to the wrong credentials
593
593
594 Throws Abort exception if login failed for any other reason.
594 Throws Abort exception if login failed for any other reason.
595
595
596 Immediately returns False if password is empty
596 Immediately returns False if password is empty
597 """
597 """
598 if not password:
598 if not password:
599 return False
599 return False
600 try:
600 try:
601 ui.note(_('(authenticating to mail server as %s)\n') %
601 ui.note(_('(authenticating to mail server as %s)\n') %
602 (username))
602 (username))
603 smtp_obj.login(username, password)
603 smtp_obj.login(username, password)
604 return True
604 return True
605 except smtplib.SMTPException, inst:
605 except smtplib.SMTPException, inst:
606 if inst.smtp_code == 535:
606 if inst.smtp_code == 535:
607 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
607 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
608 return False
608 return False
609 else:
609 else:
610 raise util.Abort(inst)
610 raise error.Abort(inst)
611
611
612
612
613 def keyring_supported_smtp(ui, username):
613 def keyring_supported_smtp(ui, username):
614 """
614 """
615 keyring-integrated replacement for mercurial.mail._smtp
615 keyring-integrated replacement for mercurial.mail._smtp
616 Used only when configuration file contains username, but
616 Used only when configuration file contains username, but
617 does not contain the password.
617 does not contain the password.
618
618
619 Most of the routine below is copied as-is from
619 Most of the routine below is copied as-is from
620 mercurial.mail._smtp. The only changed part is
620 mercurial.mail._smtp. The only changed part is
621 marked with # >>>>> and # <<<<< markers
621 marked with # >>>>> and # <<<<< markers
622 """
622 """
623 local_hostname = ui.config('smtp', 'local_hostname')
623 local_hostname = ui.config('smtp', 'local_hostname')
624 tls = ui.config('smtp', 'tls', 'none')
624 tls = ui.config('smtp', 'tls', 'none')
625 # backward compatible: when tls = true, we use starttls.
625 # backward compatible: when tls = true, we use starttls.
626 starttls = tls == 'starttls' or util.parsebool(tls)
626 starttls = tls == 'starttls' or util.parsebool(tls)
627 smtps = tls == 'smtps'
627 smtps = tls == 'smtps'
628 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
628 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
629 raise util.Abort(_("can't use TLS: Python SSL support not installed"))
629 raise error.Abort(_("can't use TLS: Python SSL support not installed"))
630 mailhost = ui.config('smtp', 'host')
630 mailhost = ui.config('smtp', 'host')
631 if not mailhost:
631 if not mailhost:
632 raise util.Abort(_('smtp.host not configured - cannot send mail'))
632 raise error.Abort(_('smtp.host not configured - cannot send mail'))
633 verifycert = ui.config('smtp', 'verifycert', 'strict')
633 verifycert = ui.config('smtp', 'verifycert', 'strict')
634 if verifycert not in ['strict', 'loose']:
634 if verifycert not in ['strict', 'loose']:
635 if util.parsebool(verifycert) is not False:
635 if util.parsebool(verifycert) is not False:
636 raise util.Abort(_('invalid smtp.verifycert configuration: %s')
636 raise error.Abort(_('invalid smtp.verifycert configuration: %s')
637 % (verifycert))
637 % (verifycert))
638 verifycert = False
638 verifycert = False
639 if getattr(sslutil, 'sslkwargs', None) is None:
639 if getattr(sslutil, 'sslkwargs', None) is None:
640 sslkwargs = None
640 sslkwargs = None
641 elif (starttls or smtps) and verifycert:
641 elif (starttls or smtps) and verifycert:
642 sslkwargs = sslutil.sslkwargs(ui, mailhost)
642 sslkwargs = sslutil.sslkwargs(ui, mailhost)
643 else:
643 else:
644 sslkwargs = {}
644 sslkwargs = {}
645 if smtps:
645 if smtps:
646 ui.note(_('(using smtps)\n'))
646 ui.note(_('(using smtps)\n'))
647
647
648 # mercurial 3.8 added a mandatory host arg
648 # mercurial 3.8 added a mandatory host arg
649 if not sslkwargs:
649 if not sslkwargs:
650 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
650 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
651 elif 'host' in SMTPS.__init__.__code__.co_varnames:
651 elif 'host' in SMTPS.__init__.__code__.co_varnames:
652 s = SMTPS(sslkwargs, local_hostname=local_hostname, host=mailhost)
652 s = SMTPS(sslkwargs, local_hostname=local_hostname, host=mailhost)
653 else:
653 else:
654 s = SMTPS(sslkwargs, local_hostname=local_hostname)
654 s = SMTPS(sslkwargs, local_hostname=local_hostname)
655 elif starttls:
655 elif starttls:
656 if not sslkwargs:
656 if not sslkwargs:
657 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
657 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
658 elif 'host' in STARTTLS.__init__.__code__.co_varnames:
658 elif 'host' in STARTTLS.__init__.__code__.co_varnames:
659 s = STARTTLS(sslkwargs, local_hostname=local_hostname, host=mailhost)
659 s = STARTTLS(sslkwargs, local_hostname=local_hostname, host=mailhost)
660 else:
660 else:
661 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
661 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
662 else:
662 else:
663 s = smtplib.SMTP(local_hostname=local_hostname)
663 s = smtplib.SMTP(local_hostname=local_hostname)
664 if smtps:
664 if smtps:
665 defaultport = 465
665 defaultport = 465
666 else:
666 else:
667 defaultport = 25
667 defaultport = 25
668 mailport = util.getport(ui.config('smtp', 'port', defaultport))
668 mailport = util.getport(ui.config('smtp', 'port', defaultport))
669 ui.note(_('sending mail: smtp host %s, port %s\n') %
669 ui.note(_('sending mail: smtp host %s, port %s\n') %
670 (mailhost, mailport))
670 (mailhost, mailport))
671 s.connect(host=mailhost, port=mailport)
671 s.connect(host=mailhost, port=mailport)
672 if starttls:
672 if starttls:
673 ui.note(_('(using starttls)\n'))
673 ui.note(_('(using starttls)\n'))
674 s.ehlo()
674 s.ehlo()
675 s.starttls()
675 s.starttls()
676 s.ehlo()
676 s.ehlo()
677 if (starttls or smtps) and verifycert:
677 if (starttls or smtps) and verifycert:
678 ui.note(_('(verifying remote certificate)\n'))
678 ui.note(_('(verifying remote certificate)\n'))
679 if getattr(sslutil, 'validatesocket', None):
679 if getattr(sslutil, 'validatesocket', None):
680 sslutil.validatesocket(s.sock)
680 sslutil.validatesocket(s.sock)
681 else:
681 else:
682 validator(ui, mailhost)(s.sock, verifycert == 'strict')
682 validator(ui, mailhost)(s.sock, verifycert == 'strict')
683
683
684 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
684 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
685 stored = password = password_store.get_smtp_password(
685 stored = password = password_store.get_smtp_password(
686 mailhost, mailport, username)
686 mailhost, mailport, username)
687 # No need to check whether password was found as try_smtp_login
687 # No need to check whether password was found as try_smtp_login
688 # just returns False if it is absent.
688 # just returns False if it is absent.
689 while not try_smtp_login(ui, s, username, password):
689 while not try_smtp_login(ui, s, username, password):
690 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
690 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
691
691
692 if stored != password:
692 if stored != password:
693 password_store.set_smtp_password(
693 password_store.set_smtp_password(
694 mailhost, mailport, username, password)
694 mailhost, mailport, username, password)
695 # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
695 # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
696
696
697 def send(sender, recipients, msg):
697 def send(sender, recipients, msg):
698 try:
698 try:
699 return s.sendmail(sender, recipients, msg)
699 return s.sendmail(sender, recipients, msg)
700 except smtplib.SMTPRecipientsRefused, inst:
700 except smtplib.SMTPRecipientsRefused, inst:
701 recipients = [r[1] for r in inst.recipients.values()]
701 recipients = [r[1] for r in inst.recipients.values()]
702 raise util.Abort('\n' + '\n'.join(recipients))
702 raise error.Abort('\n' + '\n'.join(recipients))
703 except smtplib.SMTPException, inst:
703 except smtplib.SMTPException, inst:
704 raise util.Abort(inst)
704 raise error.Abort(inst)
705
705
706 return send
706 return send
707
707
708 ############################################################
708 ############################################################
709 # SMTP monkeypatching
709 # SMTP monkeypatching
710 ############################################################
710 ############################################################
711
711
712
712
713 @monkeypatch_method(mail)
713 @monkeypatch_method(mail)
714 def _smtp(ui):
714 def _smtp(ui):
715 """
715 """
716 build an smtp connection and return a function to send email
716 build an smtp connection and return a function to send email
717
717
718 This is the monkeypatched version of _smtp(ui) function from
718 This is the monkeypatched version of _smtp(ui) function from
719 mercurial/mail.py. It calls the original unless username
719 mercurial/mail.py. It calls the original unless username
720 without password is given in the configuration.
720 without password is given in the configuration.
721 """
721 """
722 username = ui.config('smtp', 'username')
722 username = ui.config('smtp', 'username')
723 password = ui.config('smtp', 'password')
723 password = ui.config('smtp', 'password')
724
724
725 if username and not password:
725 if username and not password:
726 return keyring_supported_smtp(ui, username)
726 return keyring_supported_smtp(ui, username)
727 else:
727 else:
728 return _smtp.orig(ui)
728 return _smtp.orig(ui)
729
729
730
730
731 ############################################################
731 ############################################################
732 # Custom commands
732 # Custom commands
733 ############################################################
733 ############################################################
734
734
735 cmdtable = {}
735 cmdtable = {}
736 command = meu.command(cmdtable)
736 command = meu.command(cmdtable)
737
737
738
738
739 @command('keyring_check',
739 @command('keyring_check',
740 [],
740 [],
741 _("keyring_check [PATH]"),
741 _("keyring_check [PATH]"),
742 optionalrepo=True)
742 optionalrepo=True)
743 def cmd_keyring_check(ui, repo, *path_args, **opts): # pylint: disable=unused-argument
743 def cmd_keyring_check(ui, repo, *path_args, **opts): # pylint: disable=unused-argument
744 """
744 """
745 Prints basic info (whether password is currently saved, and how is
745 Prints basic info (whether password is currently saved, and how is
746 it identified) for given path.
746 it identified) for given path.
747
747
748 Can be run without parameters to show status for all (current repository) paths which
748 Can be run without parameters to show status for all (current repository) paths which
749 are HTTP-like.
749 are HTTP-like.
750 """
750 """
751 defined_paths = [(name, url)
751 defined_paths = [(name, url)
752 for name, url in ui.configitems('paths')]
752 for name, url in ui.configitems('paths')]
753 if path_args:
753 if path_args:
754 # Maybe parameter is an alias
754 # Maybe parameter is an alias
755 defined_paths_dic = dict(defined_paths)
755 defined_paths_dic = dict(defined_paths)
756 paths = [(path_arg, defined_paths_dic.get(path_arg, path_arg))
756 paths = [(path_arg, defined_paths_dic.get(path_arg, path_arg))
757 for path_arg in path_args]
757 for path_arg in path_args]
758 else:
758 else:
759 if not repo:
759 if not repo:
760 ui.status(_("Url to check not specified. Either run ``hg keyring_check https://...``, or run the command inside some repository (to test all defined paths).\n"))
760 ui.status(_("Url to check not specified. Either run ``hg keyring_check https://...``, or run the command inside some repository (to test all defined paths).\n"))
761 return
761 return
762 paths = [(name, url) for name, url in defined_paths]
762 paths = [(name, url) for name, url in defined_paths]
763
763
764 if not paths:
764 if not paths:
765 ui.status(_("keyring_check: no paths defined\n"))
765 ui.status(_("keyring_check: no paths defined\n"))
766 return
766 return
767
767
768 handler = HTTPPasswordHandler()
768 handler = HTTPPasswordHandler()
769
769
770 ui.status(_("keyring password save status:\n"))
770 ui.status(_("keyring password save status:\n"))
771 for name, url in paths:
771 for name, url in paths:
772 if not is_http_path(url):
772 if not is_http_path(url):
773 if path_args:
773 if path_args:
774 ui.status(_(" %s: non-http path (%s)\n") % (name, url))
774 ui.status(_(" %s: non-http path (%s)\n") % (name, url))
775 continue
775 continue
776 user, pwd, source, final_url = handler.get_credentials(
776 user, pwd, source, final_url = handler.get_credentials(
777 make_passwordmgr(ui), name, url)
777 make_passwordmgr(ui), name, url)
778 if pwd:
778 if pwd:
779 ui.status(_(" %s: password available, source: %s, bound to user %s, url %s\n") % (
779 ui.status(_(" %s: password available, source: %s, bound to user %s, url %s\n") % (
780 name, source, user, final_url))
780 name, source, user, final_url))
781 elif user:
781 elif user:
782 ui.status(_(" %s: password not available, once entered, will be bound to user %s, url %s\n") % (
782 ui.status(_(" %s: password not available, once entered, will be bound to user %s, url %s\n") % (
783 name, user, final_url))
783 name, user, final_url))
784 else:
784 else:
785 ui.status(_(" %s: password not available, user unknown, url %s\n") % (
785 ui.status(_(" %s: password not available, user unknown, url %s\n") % (
786 name, final_url))
786 name, final_url))
787
787
788
788
789 @command('keyring_clear',
789 @command('keyring_clear',
790 [],
790 [],
791 _('hg keyring_clear PATH-OR-ALIAS'),
791 _('hg keyring_clear PATH-OR-ALIAS'),
792 optionalrepo=True)
792 optionalrepo=True)
793 def cmd_keyring_clear(ui, repo, path, **opts): # pylint: disable=unused-argument
793 def cmd_keyring_clear(ui, repo, path, **opts): # pylint: disable=unused-argument
794 """
794 """
795 Drops password bound to given path (if any is saved).
795 Drops password bound to given path (if any is saved).
796
796
797 Parameter can be given as full url (``https://John@bitbucket.org``) or as the name
797 Parameter can be given as full url (``https://John@bitbucket.org``) or as the name
798 of path alias (``bitbucket``).
798 of path alias (``bitbucket``).
799 """
799 """
800 path_url = path
800 path_url = path
801 for name, url in ui.configitems('paths'):
801 for name, url in ui.configitems('paths'):
802 if name == path:
802 if name == path:
803 path_url = url
803 path_url = url
804 break
804 break
805 if not is_http_path(path_url):
805 if not is_http_path(path_url):
806 ui.status(_("%s is not a http path (and %s can't be resolved as path alias)\n") % (path, path_url))
806 ui.status(_("%s is not a http path (and %s can't be resolved as path alias)\n") % (path, path_url))
807 return
807 return
808
808
809 handler = HTTPPasswordHandler()
809 handler = HTTPPasswordHandler()
810
810
811 user, pwd, source, final_url = handler.get_credentials(
811 user, pwd, source, final_url = handler.get_credentials(
812 make_passwordmgr(ui), path, path_url)
812 make_passwordmgr(ui), path, path_url)
813 if not user:
813 if not user:
814 ui.status(_("Username not configured for url %s\n") % final_url)
814 ui.status(_("Username not configured for url %s\n") % final_url)
815 return
815 return
816 if not pwd:
816 if not pwd:
817 ui.status(_("No password is saved for user %s, url %s\n") % (
817 ui.status(_("No password is saved for user %s, url %s\n") % (
818 user, final_url))
818 user, final_url))
819 return
819 return
820
820
821 if source != handler.SRC_KEYRING:
821 if source != handler.SRC_KEYRING:
822 ui.status(_("Password for user %s, url %s is saved in %s, not in keyring\n") % (
822 ui.status(_("Password for user %s, url %s is saved in %s, not in keyring\n") % (
823 user, final_url, source))
823 user, final_url, source))
824
824
825 password_store.clear_http_password(final_url, user)
825 password_store.clear_http_password(final_url, user)
826 ui.status(_("Password removed for user %s, url %s\n") % (
826 ui.status(_("Password removed for user %s, url %s\n") % (
827 user, final_url))
827 user, final_url))
828
828
829
829
830 buglink = 'https://bitbucket.org/Mekk/mercurial_keyring/issues'
830 buglink = 'https://bitbucket.org/Mekk/mercurial_keyring/issues'
General Comments 0
You need to be logged in to leave comments. Login now