##// END OF EJS Templates
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
Marcin Kasperski -
r10:d7564724 default
parent child Browse files
Show More
@@ -58,9 +58,7 b' def monkeypatch_method(cls):'
58
58
59 class PasswordStore(object):
59 class PasswordStore(object):
60 """
60 """
61 Helper object handling password save&restore. Passwords
61 Helper object handling keyring usage (password save&restore).
62 are saved both in local memory cache, and keyring, and are
63 restored from those.
64 """
62 """
65 def __init__(self):
63 def __init__(self):
66 self.cache = dict()
64 self.cache = dict()
@@ -80,6 +78,144 b' password_store = PasswordStore()'
80
78
81 ############################################################
79 ############################################################
82
80
81 class PasswordHandler(object):
82 """
83 Actual implementation of password handling (user prompting,
84 configuration file searching, keyring save&restore).
85
86 Object of this class is bound as passwordmgr attribute.
87 """
88 def __init__(self):
89 self.pwd_cache = {}
90 self.last_reply = None
91
92 def find_auth(self, pwmgr, realm, authuri):
93 """
94 Actual implementation of find_user_password
95 """
96 ui = pwmgr.ui
97
98 # If we are called again just after identical previous request,
99 # then the previously returned auth must have been wrong. So we
100 # note this to force password prompt
101 after_bad_auth = (self.last_reply \
102 and (self.last_reply['realm'] == realm) \
103 and (self.last_reply['authuri'] == authuri))
104
105 base_url = self.canonical_url(authuri)
106
107 # Extracting possible username (or password)
108 # stored in directly in repository url
109 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(pwmgr, realm, authuri)
110 if user and 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)
113 return user, pwd
114
115 # Checking the memory cache (there may be many http calls per command)
116 cache_key = (realm, base_url)
117 if not after_bad_auth:
118 cached_auth = self.pwd_cache.get(cache_key)
119 if cached_auth:
120 user, pwd = cached_auth
121 self._debug_reply(ui, _("Cached auth data found"), base_url, user, pwd)
122 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
123 return user, pwd
124
125 # Loading username and maybe password from [auth]
126 nuser, pwd = self.load_hgrc_auth(ui, base_url)
127 if nuser:
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)))
130 user = nuser
131 if pwd:
132 self.pwd_cache[cache_key] = 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)
135 return user, pwd
136 else:
137 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
138
139 # If username is known, and we are not after failure, we can try keyring
140 if user and not after_bad_auth:
141 pwd = password_store.get_password(base_url, user)
142 if pwd:
143 self.pwd_cache[cache_key] = 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)
146 return user, pwd
147
148 fixed_user = (user and True or False)
149
150 # Last resort: interactive prompt
151 if not ui.interactive():
152 raise util.Abort(_('mercurial_keyring: http authorization required'))
153 ui.write(_("http authorization required\n"))
154 ui.status(_("realm: %s\n") % realm)
155 if fixed_user:
156 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
157 else:
158 user = ui.prompt(_("user:"), default=None)
159 pwd = ui.getpass(_("password: "))
160
161 if fixed_user:
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
164 # preserve it
165 ui.debug("Saving password for %s to keyring\n" % user)
166 password_store.set_password(base_url, user, pwd)
167
168 self.pwd_cache[cache_key] = 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)
171 return user, pwd
172
173 def load_hgrc_auth(self, ui, base_url):
174 """
175 Loading username and possibly password from [auth] in local
176 repo .hgrc
177 """
178 # Lines below unfortunately do not work, readauthtoken
179 # always return None. Why? Because
180 # ui (self.ui of passwordmgr) describes the *remote* repository, so
181 # does *not* contain any option from local .hg/hgrc.
182
183 #auth_token = self.readauthtoken(base_url)
184 #if auth_token:
185 # user, pwd = auth.get('username'), auth.get('password')
186
187 # Workaround: we recreate the repository object
188 repo_root = ui.config("bundle", "mainreporoot")
189 if repo_root:
190 from mercurial.ui import ui as _ui
191 import os
192 local_ui = _ui(ui)
193 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
194 local_passwordmgr = passwordmgr(local_ui)
195 auth_token = local_passwordmgr.readauthtoken(base_url)
196 if auth_token:
197 return auth_token.get('username'), auth_token.get('password')
198 return None, None
199
200
201 def canonical_url(self, authuri):
202 """
203 Strips query params from url. Used to convert
204 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
205 to
206 https://repo.machine.com/repos/apps/module
207 """
208 parsed_url = urlparse(authuri)
209 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
210
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'))
213
214 ############################################################
215
216 # The idea: if we are re-asked with exactly the same params
217 # (authuri, not base_url) then password must have been wrong.
218
83 @monkeypatch_method(passwordmgr)
219 @monkeypatch_method(passwordmgr)
84 def find_user_password(self, realm, authuri):
220 def find_user_password(self, realm, authuri):
85 """
221 """
@@ -88,85 +224,12 b' def find_user_password(self, realm, auth'
88 Passwords are saved in gnome keyring, OSX/Chain or other platform
224 Passwords are saved in gnome keyring, OSX/Chain or other platform
89 specific storage and keyed by the repository url
225 specific storage and keyed by the repository url
90 """
226 """
91 # Calculate the true remote url. authuri happens to contain things like
227 # Extend object attributes
92 # https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
228 if not hasattr(self, '_pwd_handler'):
93 parsed_url = urlparse(authuri)
229 self._pwd_handler = PasswordHandler()
94 base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
95
96 # Extracting possible username/password stored in directly in repository url
97 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
98
99 # Checking the local cache (single command may repeat the call many
100 # times)
101 if not hasattr(self, '_pwd_cache'):
102 self._pwd_cache = {}
103 cache_key = (realm, base_url)
104 cached_auth = self._pwd_cache.get(cache_key)
105 if cached_auth:
106 self.ui.debug("Found cached auth tokens for %s: %s, %s\n" % (
107 base_url, cached_auth[0], cached_auth[1] and '********' or ''))
108 return cached_auth
109
230
110 # Loading username (and maybe password) from [auth] in local .hg/hgrc
231 return self._pwd_handler.find_auth(self, realm, authuri)
111 if not user:
112 # Lines below unfortunately do not work, readauthtoken
113 # always return None. Why? Because
114 # self.ui here describes the *remote* repository, so
115 # does *not* contain any option from local .hg/hgrc.
116 #
117 #auth_token = self.readauthtoken(base_url)
118 #if auth_token:
119 # user, pwd = auth.get('username'), auth.get('password')
120 #
121 # so - workaround
122 repo_root = self.ui.config("bundle", "mainreporoot")
123 if repo_root:
124 from mercurial.ui import ui as _ui
125 import os
126 local_ui = _ui(self.ui)
127 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
128 local_passwordmgr = passwordmgr(local_ui)
129 auth_token = local_passwordmgr.readauthtoken(base_url)
130 if auth_token:
131 user, pwd = auth_token.get('username'), auth_token.get('password')
132 self.ui.debug("Found .hg/hgrc auth tokens: %s, %s\n" % (
133 user, pwd and '********' or ''))
134
232
135 # username still not known? Asking
136 prompted = False
137 if not user:
138 if not self.ui.interactive():
139 raise util.Abort(_('mercurial_keyring: http authorization required'))
140 self.ui.write(_("http authorization required\n"))
141 self.ui.status(_("realm: %s\n") % realm)
142 user = self.ui.prompt(_("user:"), default=None)
143 prompted = True
144
145 # username known and still no password? Time to check keyring
146 if user and not pwd:
147 pwd = password_store.get_password(base_url, user)
148 if pwd:
149 self.ui.debug("Found keyring password for %s\n" % user)
150
151 # password still not known? Asking
152 if not pwd:
153 if not prompted:
154 if not self.ui.interactive():
155 raise util.Abort(_('mercurial_keyring: http authorization required'))
156 self.ui.write(_("http authorization required\n"))
157 self.ui.status(_("realm: %s\n") % realm)
158 pwd = self.ui.getpass(_("password for %s: ") % user)
159 else:
160 pwd = self.ui.getpass(_("password: "))
161 password_store.set_password(base_url, user, pwd)
162 self.ui.debug("Saved keyring password for %s\n" % user)
163
164 self._pwd_cache[cache_key] = (user, pwd)
165
166 self.ui.debug("Returning auth tokens for %s: %s, %s\n" % (
167 base_url, user, pwd and '********' or ''))
168 return user, pwd
169 #return None, None
170
233
171 ############################################################
234 ############################################################
172
235
General Comments 0
You need to be logged in to leave comments. Login now