##// END OF EJS Templates
Some reformatting (shortening long lines, slightly reorganised docs
Marcin Kasperski -
r18:d4b7d433 default
parent child Browse files
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 in password databases (Gnome Keyring, KDE KWallet, OSXKeyChain,
16 password databases (Gnome Keyring, KDE KWallet, OSXKeyChain, specific
17 specific solutions for Win32 and command line). Uses and wraps
17 solutions for Win32 and command line). Uses and wraps services of the
18 services of the keyring_ library.
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 invalid,
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 user
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 repository
77 Edit repository-local ``.hg/hgrc`` and save there the remote
78 path and the username, but do not save the password. For example:
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``, extension
98 Note: if both username and password are given in ``.hg/hgrc``,
98 will use them without using the password database. If username is not
99 extension will use them without using the password database. If
99 given, extension will prompt for credentials every time, also without
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 to replace the find_user_password method.
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(pwmgr, realm, authuri)
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"), base_url, user, pwd)
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"), base_url, user, pwd)
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"), base_url, user, pwd)
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 not after failure (so
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"), base_url, user, pwd)
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 fixed. Otherwise we won't
260 # It is done only if username is permanently set.
254 # be able to find the password so it does not make much sense to
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"), base_url, user, pwd)
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" % (msg, url, user, pwd and '*' * len(pwd) or 'not set'))
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