##// END OF EJS Templates
cleaning not needed patch
Marcin Kasperski -
r11:669009c3 default
parent child Browse files
Show More
@@ -1,248 +1,232 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 """
3 """
4 Storing HTTP authentication passwords in keyring database.
4 Storing HTTP authentication passwords in keyring database.
5
5
6 Installation method(s):
6 Installation method(s):
7
7
8 1) in ~/.hgrc (or /etc/hgext/...)
8 1) in ~/.hgrc (or /etc/hgext/...)
9
9
10 [extensions]
10 [extensions]
11 ...
11 ...
12 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
12 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
13
13
14
14
15 2) Drop this file to hgext directory and in ~/.hgrc
15 2) Drop this file to hgext directory and in ~/.hgrc
16
16
17 [extensions]
17 [extensions]
18 hgext.mercurial_keyring =
18 hgext.mercurial_keyring =
19
19
20 """
20 """
21
21
22 #import mercurial.demandimport
22 #import mercurial.demandimport
23 #mercurial.demandimport.disable()
23 #mercurial.demandimport.disable()
24
24
25 from mercurial import hg, repo, util
25 from mercurial import hg, repo, util
26 from mercurial.i18n import _
26 from mercurial.i18n import _
27 try:
27 try:
28 from mercurial.url import passwordmgr
28 from mercurial.url import passwordmgr
29 except:
29 except:
30 from mercurial.httprepo import passwordmgr
30 from mercurial.httprepo import passwordmgr
31 from mercurial.httprepo import httprepository
31 from mercurial.httprepo import httprepository
32
32
33 import keyring
33 import keyring
34 import getpass
34 import getpass
35 from urlparse import urlparse
35 from urlparse import urlparse
36 import urllib2
36 import urllib2
37
37
38 KEYRING_SERVICE = "Mercurial"
38 KEYRING_SERVICE = "Mercurial"
39
39
40 ############################################################
40 ############################################################
41
41
42 def monkeypatch_class(name, bases, namespace):
42 def monkeypatch_class(name, bases, namespace):
43 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
43 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
44 assert len(bases) == 1, "Exactly one base class required"
44 assert len(bases) == 1, "Exactly one base class required"
45 base = bases[0]
45 base = bases[0]
46 for name, value in namespace.iteritems():
46 for name, value in namespace.iteritems():
47 if name != "__metaclass__":
47 if name != "__metaclass__":
48 setattr(base, name, value)
48 setattr(base, name, value)
49 return base
49 return base
50
50
51 def monkeypatch_method(cls):
51 def monkeypatch_method(cls):
52 def decorator(func):
52 def decorator(func):
53 setattr(cls, func.__name__, func)
53 setattr(cls, func.__name__, func)
54 return func
54 return func
55 return decorator
55 return decorator
56
56
57 ############################################################
57 ############################################################
58
58
59 class PasswordStore(object):
59 class PasswordStore(object):
60 """
60 """
61 Helper object handling keyring usage (password save&restore).
61 Helper object handling keyring usage (password save&restore).
62 """
62 """
63 def __init__(self):
63 def __init__(self):
64 self.cache = dict()
64 self.cache = dict()
65 def get_password(self, url, username):
65 def get_password(self, url, username):
66 return keyring.get_password(KEYRING_SERVICE,
66 return keyring.get_password(KEYRING_SERVICE,
67 self._format_key(url, username))
67 self._format_key(url, username))
68 def set_password(self, url, username, password):
68 def set_password(self, url, username, password):
69 keyring.set_password(KEYRING_SERVICE,
69 keyring.set_password(KEYRING_SERVICE,
70 self._format_key(url, username),
70 self._format_key(url, username),
71 password)
71 password)
72 def clear_password(self, url, username):
72 def clear_password(self, url, username):
73 self.set_password(url, username, "")
73 self.set_password(url, username, "")
74 def _format_key(self, url, username):
74 def _format_key(self, url, username):
75 return "%s@@%s" % (username, url)
75 return "%s@@%s" % (username, url)
76
76
77 password_store = PasswordStore()
77 password_store = PasswordStore()
78
78
79 ############################################################
79 ############################################################
80
80
81 class PasswordHandler(object):
81 class PasswordHandler(object):
82 """
82 """
83 Actual implementation of password handling (user prompting,
83 Actual implementation of password handling (user prompting,
84 configuration file searching, keyring save&restore).
84 configuration file searching, keyring save&restore).
85
85
86 Object of this class is bound as passwordmgr attribute.
86 Object of this class is bound as passwordmgr attribute.
87 """
87 """
88 def __init__(self):
88 def __init__(self):
89 self.pwd_cache = {}
89 self.pwd_cache = {}
90 self.last_reply = None
90 self.last_reply = None
91
91
92 def find_auth(self, pwmgr, realm, authuri):
92 def find_auth(self, pwmgr, realm, authuri):
93 """
93 """
94 Actual implementation of find_user_password
94 Actual implementation of find_user_password
95 """
95 """
96 ui = pwmgr.ui
96 ui = pwmgr.ui
97
97
98 # If we are called again just after identical previous request,
98 # If we are called again just after identical previous request,
99 # then the previously returned auth must have been wrong. So we
99 # then the previously returned auth must have been wrong. So we
100 # note this to force password prompt
100 # note this to force password prompt
101 after_bad_auth = (self.last_reply \
101 after_bad_auth = (self.last_reply \
102 and (self.last_reply['realm'] == realm) \
102 and (self.last_reply['realm'] == realm) \
103 and (self.last_reply['authuri'] == authuri))
103 and (self.last_reply['authuri'] == authuri))
104
104
105 base_url = self.canonical_url(authuri)
105 base_url = self.canonical_url(authuri)
106
106
107 # Extracting possible username (or password)
107 # Extracting possible username (or password)
108 # stored in directly in repository url
108 # stored in directly in repository url
109 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(pwmgr, realm, authuri)
109 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(pwmgr, realm, authuri)
110 if user and pwd:
110 if user and pwd:
111 self._debug_reply(ui, _("Auth data found in repository URL"), base_url, user, pwd)
111 self._debug_reply(ui, _("Auth data found in repository URL"), base_url, user, pwd)
112 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
112 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
113 return user, pwd
113 return user, pwd
114
114
115 # Checking the memory cache (there may be many http calls per command)
115 # Checking the memory cache (there may be many http calls per command)
116 cache_key = (realm, base_url)
116 cache_key = (realm, base_url)
117 if not after_bad_auth:
117 if not after_bad_auth:
118 cached_auth = self.pwd_cache.get(cache_key)
118 cached_auth = self.pwd_cache.get(cache_key)
119 if cached_auth:
119 if cached_auth:
120 user, pwd = cached_auth
120 user, pwd = cached_auth
121 self._debug_reply(ui, _("Cached auth data found"), base_url, user, pwd)
121 self._debug_reply(ui, _("Cached auth data found"), base_url, user, pwd)
122 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
122 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
123 return user, pwd
123 return user, pwd
124
124
125 # Loading username and maybe password from [auth]
125 # Loading username and maybe password from [auth]
126 nuser, pwd = self.load_hgrc_auth(ui, base_url)
126 nuser, pwd = self.load_hgrc_auth(ui, base_url)
127 if nuser:
127 if nuser:
128 if user:
128 if user:
129 raise util.Abort(_('mercurial_keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, user, nuser)))
129 raise util.Abort(_('mercurial_keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, user, nuser)))
130 user = nuser
130 user = nuser
131 if pwd:
131 if pwd:
132 self.pwd_cache[cache_key] = user, pwd
132 self.pwd_cache[cache_key] = user, pwd
133 self._debug_reply(ui, _("Auth data set in .hg/hgrc"), base_url, user, pwd)
133 self._debug_reply(ui, _("Auth data set in .hg/hgrc"), base_url, user, pwd)
134 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
134 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
135 return user, pwd
135 return user, pwd
136 else:
136 else:
137 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
137 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
138
138
139 # If username is known, and we are not after failure, we can try keyring
139 # If username is known, and we are not after failure, we can try keyring
140 if user and not after_bad_auth:
140 if user and not after_bad_auth:
141 pwd = password_store.get_password(base_url, user)
141 pwd = password_store.get_password(base_url, user)
142 if pwd:
142 if pwd:
143 self.pwd_cache[cache_key] = user, pwd
143 self.pwd_cache[cache_key] = user, pwd
144 self._debug_reply(ui, _("Keyring password found"), base_url, user, pwd)
144 self._debug_reply(ui, _("Keyring password found"), base_url, user, pwd)
145 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
145 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
146 return user, pwd
146 return user, pwd
147
147
148 fixed_user = (user and True or False)
148 fixed_user = (user and True or False)
149
149
150 # Last resort: interactive prompt
150 # Last resort: interactive prompt
151 if not ui.interactive():
151 if not ui.interactive():
152 raise util.Abort(_('mercurial_keyring: http authorization required'))
152 raise util.Abort(_('mercurial_keyring: http authorization required'))
153 ui.write(_("http authorization required\n"))
153 ui.write(_("http authorization required\n"))
154 ui.status(_("realm: %s\n") % realm)
154 ui.status(_("realm: %s\n") % realm)
155 if fixed_user:
155 if fixed_user:
156 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
156 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
157 else:
157 else:
158 user = ui.prompt(_("user:"), default=None)
158 user = ui.prompt(_("user:"), default=None)
159 pwd = ui.getpass(_("password: "))
159 pwd = ui.getpass(_("password: "))
160
160
161 if fixed_user:
161 if fixed_user:
162 # We save in keyring only if username is fixed. Otherwise we won't
162 # We save in keyring only if username is fixed. Otherwise we won't
163 # be able to find the password so it does not make any sense to
163 # be able to find the password so it does not make any sense to
164 # preserve it
164 # preserve it
165 ui.debug("Saving password for %s to keyring\n" % user)
165 ui.debug("Saving password for %s to keyring\n" % user)
166 password_store.set_password(base_url, user, pwd)
166 password_store.set_password(base_url, user, pwd)
167
167
168 self.pwd_cache[cache_key] = user, pwd
168 self.pwd_cache[cache_key] = user, pwd
169 self._debug_reply(ui, _("Manually entered password"), base_url, user, pwd)
169 self._debug_reply(ui, _("Manually entered password"), base_url, user, pwd)
170 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
170 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
171 return user, pwd
171 return user, pwd
172
172
173 def load_hgrc_auth(self, ui, base_url):
173 def load_hgrc_auth(self, ui, base_url):
174 """
174 """
175 Loading username and possibly password from [auth] in local
175 Loading username and possibly password from [auth] in local
176 repo .hgrc
176 repo .hgrc
177 """
177 """
178 # Lines below unfortunately do not work, readauthtoken
178 # Lines below unfortunately do not work, readauthtoken
179 # always return None. Why? Because
179 # always return None. Why? Because
180 # ui (self.ui of passwordmgr) describes the *remote* repository, so
180 # ui (self.ui of passwordmgr) describes the *remote* repository, so
181 # does *not* contain any option from local .hg/hgrc.
181 # does *not* contain any option from local .hg/hgrc.
182
182
183 #auth_token = self.readauthtoken(base_url)
183 #auth_token = self.readauthtoken(base_url)
184 #if auth_token:
184 #if auth_token:
185 # user, pwd = auth.get('username'), auth.get('password')
185 # user, pwd = auth.get('username'), auth.get('password')
186
186
187 # Workaround: we recreate the repository object
187 # Workaround: we recreate the repository object
188 repo_root = ui.config("bundle", "mainreporoot")
188 repo_root = ui.config("bundle", "mainreporoot")
189 if repo_root:
189 if repo_root:
190 from mercurial.ui import ui as _ui
190 from mercurial.ui import ui as _ui
191 import os
191 import os
192 local_ui = _ui(ui)
192 local_ui = _ui(ui)
193 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
193 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
194 local_passwordmgr = passwordmgr(local_ui)
194 local_passwordmgr = passwordmgr(local_ui)
195 auth_token = local_passwordmgr.readauthtoken(base_url)
195 auth_token = local_passwordmgr.readauthtoken(base_url)
196 if auth_token:
196 if auth_token:
197 return auth_token.get('username'), auth_token.get('password')
197 return auth_token.get('username'), auth_token.get('password')
198 return None, None
198 return None, None
199
199
200
200
201 def canonical_url(self, authuri):
201 def canonical_url(self, authuri):
202 """
202 """
203 Strips query params from url. Used to convert
203 Strips query params from url. Used to convert
204 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
204 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
205 to
205 to
206 https://repo.machine.com/repos/apps/module
206 https://repo.machine.com/repos/apps/module
207 """
207 """
208 parsed_url = urlparse(authuri)
208 parsed_url = urlparse(authuri)
209 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
209 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
210
210
211 def _debug_reply(self, ui, msg, url, user, pwd):
211 def _debug_reply(self, ui, msg, url, user, pwd):
212 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (msg, url, user, pwd and '*' * len(pwd) or 'not set'))
212 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (msg, url, user, pwd and '*' * len(pwd) or 'not set'))
213
213
214 ############################################################
214 ############################################################
215
215
216 # The idea: if we are re-asked with exactly the same params
216 # The idea: if we are re-asked with exactly the same params
217 # (authuri, not base_url) then password must have been wrong.
217 # (authuri, not base_url) then password must have been wrong.
218
218
219 @monkeypatch_method(passwordmgr)
219 @monkeypatch_method(passwordmgr)
220 def find_user_password(self, realm, authuri):
220 def find_user_password(self, realm, authuri):
221 """
221 """
222 keyring-based implementation of username/password query
222 keyring-based implementation of username/password query
223
223
224 Passwords are saved in gnome keyring, OSX/Chain or other platform
224 Passwords are saved in gnome keyring, OSX/Chain or other platform
225 specific storage and keyed by the repository url
225 specific storage and keyed by the repository url
226 """
226 """
227 # Extend object attributes
227 # Extend object attributes
228 if not hasattr(self, '_pwd_handler'):
228 if not hasattr(self, '_pwd_handler'):
229 self._pwd_handler = PasswordHandler()
229 self._pwd_handler = PasswordHandler()
230
230
231 return self._pwd_handler.find_auth(self, realm, authuri)
231 return self._pwd_handler.find_auth(self, realm, authuri)
232
232
233
234 ############################################################
235
236 # We patch httprespository.do_cmd to grab information that
237 # the request failed due to wrong auth - and clear the wrong
238 # password
239
240 orig_do_cmd = httprepository.do_cmd
241
242 @monkeypatch_method(httprepository)
243 def do_cmd(self, cmd, **args):
244 try:
245 orig_do_cmd(self, cmd, **args)
246 except util.Abort, e:
247 self.ui.debug("Authorization failed for %s\n" % self.url())
248 raise
General Comments 0
You need to be logged in to leave comments. Login now