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