Show More
@@ -12,10 +12,10 b'' | |||||
12 | mercurial_keyring |
|
12 | mercurial_keyring | |
13 | ================= |
|
13 | ================= | |
14 |
|
14 | |||
15 | Mercurial extension to securely save HTTP authentication passwords |
|
15 | Mercurial extension to securely save HTTP authentication passwords in | |
16 |
|
|
16 | password databases (Gnome Keyring, KDE KWallet, OSXKeyChain, specific | |
17 |
|
|
17 | solutions for Win32 and command line). Uses and wraps services of the | |
18 |
|
|
18 | keyring_ library. | |
19 |
|
19 | |||
20 | .. _keyring: http://pypi.python.org/pypi/keyring |
|
20 | .. _keyring: http://pypi.python.org/pypi/keyring | |
21 |
|
21 | |||
@@ -29,9 +29,9 b' database. On the next run it checks for ' | |||||
29 | then for suitable password in the password database, and uses those |
|
29 | then for suitable password in the password database, and uses those | |
30 | credentials if found. |
|
30 | credentials if found. | |
31 |
|
31 | |||
32 |
In case password turns out incorrect (either because it was |
|
32 | In case password turns out to be incorrect (either because it was | |
33 |
or because it was changed on the server) it just prompts the |
|
33 | invalid, or because it was changed on the server) it just prompts the | |
34 | again. |
|
34 | user again. | |
35 |
|
35 | |||
36 | Installation |
|
36 | Installation | |
37 | ============ |
|
37 | ============ | |
@@ -74,8 +74,9 b' open to suggestions.*' | |||||
74 | Repository configuration |
|
74 | Repository configuration | |
75 | ======================== |
|
75 | ======================== | |
76 |
|
76 | |||
77 |
Edit repository-local ``.hg/hgrc`` and save there the remote |
|
77 | Edit repository-local ``.hg/hgrc`` and save there the remote | |
78 |
path and the username, but do not save the password. For |
|
78 | repository path and the username, but do not save the password. For | |
|
79 | example: | |||
79 |
|
80 | |||
80 | :: |
|
81 | :: | |
81 |
|
82 | |||
@@ -94,23 +95,24 b' Simpler form with url-embedded name can ' | |||||
94 | [paths] |
|
95 | [paths] | |
95 | bitbucket = https://User@bitbucket.org/User/project_name/ |
|
96 | bitbucket = https://User@bitbucket.org/User/project_name/ | |
96 |
|
97 | |||
97 |
Note: if both username and password are given in ``.hg/hgrc``, |
|
98 | Note: if both username and password are given in ``.hg/hgrc``, | |
98 |
will use them without using the password database. If |
|
99 | extension will use them without using the password database. If | |
99 |
given, extension will prompt for credentials every |
|
100 | username is not given, extension will prompt for credentials every | |
100 |
saving the password. |
|
101 | time, also without saving the password. | |
101 |
|
102 | |||
102 | Usage |
|
103 | Usage | |
103 | ===== |
|
104 | ===== | |
104 |
|
105 | |||
105 | Configure the repository as above, then just pull and push. |
|
106 | Configure the repository as above, then just pull and push. | |
106 | You should be asked for the password only once (per every |
|
107 | You should be asked for the password only once (per every | |
107 | username+remote_repository_url combination). |
|
108 | username+remote_repository_url combination). | |
108 |
|
109 | |||
109 | Implementation details |
|
110 | Implementation details | |
110 | ====================== |
|
111 | ====================== | |
111 |
|
112 | |||
112 | The extension is monkey-patching the mercurial passwordmgr class |
|
113 | The extension is monkey-patching the mercurial passwordmgr class to | |
113 |
|
|
114 | replace the find_user_password method. Detailed order of operations | |
|
115 | is described in the comments inside the code. | |||
114 |
|
116 | |||
115 | """ |
|
117 | """ | |
116 |
|
118 | |||
@@ -193,9 +195,11 b' class PasswordHandler(object):' | |||||
193 |
|
195 | |||
194 | # Extracting possible username (or password) |
|
196 | # Extracting possible username (or password) | |
195 | # stored directly in repository url |
|
197 | # stored directly in repository url | |
196 |
user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( |
|
198 | user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( | |
|
199 | pwmgr, realm, authuri) | |||
197 | if user and pwd: |
|
200 | if user and pwd: | |
198 |
self._debug_reply(ui, _("Auth data found in repository URL"), |
|
201 | self._debug_reply(ui, _("Auth data found in repository URL"), | |
|
202 | base_url, user, pwd) | |||
199 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) |
|
203 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) | |
200 | return user, pwd |
|
204 | return user, pwd | |
201 |
|
205 | |||
@@ -205,7 +209,8 b' class PasswordHandler(object):' | |||||
205 | cached_auth = self.pwd_cache.get(cache_key) |
|
209 | cached_auth = self.pwd_cache.get(cache_key) | |
206 | if cached_auth: |
|
210 | if cached_auth: | |
207 | user, pwd = cached_auth |
|
211 | user, pwd = cached_auth | |
208 |
self._debug_reply(ui, _("Cached auth data found"), |
|
212 | self._debug_reply(ui, _("Cached auth data found"), | |
|
213 | base_url, user, pwd) | |||
209 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) |
|
214 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) | |
210 | return user, pwd |
|
215 | return user, pwd | |
211 |
|
216 | |||
@@ -217,20 +222,22 b' class PasswordHandler(object):' | |||||
217 | user = nuser |
|
222 | user = nuser | |
218 | if pwd: |
|
223 | if pwd: | |
219 | self.pwd_cache[cache_key] = user, pwd |
|
224 | self.pwd_cache[cache_key] = user, pwd | |
220 |
self._debug_reply(ui, _("Auth data set in .hg/hgrc"), |
|
225 | self._debug_reply(ui, _("Auth data set in .hg/hgrc"), | |
|
226 | base_url, user, pwd) | |||
221 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) |
|
227 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) | |
222 | return user, pwd |
|
228 | return user, pwd | |
223 | else: |
|
229 | else: | |
224 | ui.debug(_("Username found in .hg/hgrc: %s\n" % user)) |
|
230 | ui.debug(_("Username found in .hg/hgrc: %s\n" % user)) | |
225 |
|
231 | |||
226 | # Loading password from keyring. |
|
232 | # Loading password from keyring. | |
227 |
# Only if username is known (so we know the key) and we are |
|
233 | # Only if username is known (so we know the key) and we are | |
228 | # we don't reuse the bad password). |
|
234 | # not after failure (so we don't reuse the bad password). | |
229 | if user and not after_bad_auth: |
|
235 | if user and not after_bad_auth: | |
230 | pwd = password_store.get_password(base_url, user) |
|
236 | pwd = password_store.get_password(base_url, user) | |
231 | if pwd: |
|
237 | if pwd: | |
232 | self.pwd_cache[cache_key] = user, pwd |
|
238 | self.pwd_cache[cache_key] = user, pwd | |
233 |
self._debug_reply(ui, _("Keyring password found"), |
|
239 | self._debug_reply(ui, _("Keyring password found"), | |
|
240 | base_url, user, pwd) | |||
234 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) |
|
241 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) | |
235 | return user, pwd |
|
242 | return user, pwd | |
236 |
|
243 | |||
@@ -250,16 +257,17 b' class PasswordHandler(object):' | |||||
250 |
|
257 | |||
251 | if fixed_user: |
|
258 | if fixed_user: | |
252 | # Saving password to the keyring. |
|
259 | # Saving password to the keyring. | |
253 |
# It is done only if username is |
|
260 | # It is done only if username is permanently set. | |
254 |
# be able to find the password so it |
|
261 | # Otherwise we won't be able to find the password so it | |
255 | # preserve it |
|
262 | # does not make much sense to preserve it | |
256 | ui.debug("Saving password for %s to keyring\n" % user) |
|
263 | ui.debug("Saving password for %s to keyring\n" % user) | |
257 | password_store.set_password(base_url, user, pwd) |
|
264 | password_store.set_password(base_url, user, pwd) | |
258 |
|
265 | |||
259 | # Saving password to the memory cache |
|
266 | # Saving password to the memory cache | |
260 | self.pwd_cache[cache_key] = user, pwd |
|
267 | self.pwd_cache[cache_key] = user, pwd | |
261 |
|
268 | |||
262 |
self._debug_reply(ui, _("Manually entered password"), |
|
269 | self._debug_reply(ui, _("Manually entered password"), | |
|
270 | base_url, user, pwd) | |||
263 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) |
|
271 | self.last_reply = dict(realm=realm,authuri=authuri,user=user) | |
264 | return user, pwd |
|
272 | return user, pwd | |
265 |
|
273 | |||
@@ -292,10 +300,9 b' class PasswordHandler(object):' | |||||
292 | return auth_token.get('username'), auth_token.get('password') |
|
300 | return auth_token.get('username'), auth_token.get('password') | |
293 | return None, None |
|
301 | return None, None | |
294 |
|
302 | |||
295 |
|
||||
296 | def canonical_url(self, authuri): |
|
303 | def canonical_url(self, authuri): | |
297 | """ |
|
304 | """ | |
298 | Strips query params from url. Used to convert |
|
305 | Strips query params from url. Used to convert urls like | |
299 | https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between |
|
306 | https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between | |
300 | to |
|
307 | to | |
301 | https://repo.machine.com/repos/apps/module |
|
308 | https://repo.machine.com/repos/apps/module | |
@@ -304,7 +311,8 b' class PasswordHandler(object):' | |||||
304 | return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path) |
|
311 | return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path) | |
305 |
|
312 | |||
306 | def _debug_reply(self, ui, msg, url, user, pwd): |
|
313 | def _debug_reply(self, ui, msg, url, user, pwd): | |
307 |
ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % ( |
|
314 | ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % ( | |
|
315 | msg, url, user, pwd and '*' * len(pwd) or 'not set')) | |||
308 |
|
316 | |||
309 | ############################################################ |
|
317 | ############################################################ | |
310 |
|
318 |
General Comments 0
You need to be logged in to leave comments.
Login now