##// END OF EJS Templates
Some diagnostics. Still - the way to detect good/bad password is needed
Marcin Kasperski -
r9:129fb242 default
parent child Browse files
Show More
@@ -1,158 +1,185 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
32
32 import keyring
33 import keyring
33 import getpass
34 import getpass
34 from urlparse import urlparse
35 from urlparse import urlparse
35 import urllib2
36 import urllib2
36
37
37 KEYRING_SERVICE = "Mercurial"
38 KEYRING_SERVICE = "Mercurial"
38
39
39 ############################################################
40 ############################################################
40
41
41 def monkeypatch_class(name, bases, namespace):
42 def monkeypatch_class(name, bases, namespace):
42 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
43 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
43 assert len(bases) == 1, "Exactly one base class required"
44 assert len(bases) == 1, "Exactly one base class required"
44 base = bases[0]
45 base = bases[0]
45 for name, value in namespace.iteritems():
46 for name, value in namespace.iteritems():
46 if name != "__metaclass__":
47 if name != "__metaclass__":
47 setattr(base, name, value)
48 setattr(base, name, value)
48 return base
49 return base
49
50
50 def monkeypatch_method(cls):
51 def monkeypatch_method(cls):
51 def decorator(func):
52 def decorator(func):
52 setattr(cls, func.__name__, func)
53 setattr(cls, func.__name__, func)
53 return func
54 return func
54 return decorator
55 return decorator
55
56
56 ############################################################
57 ############################################################
57
58
58 class PasswordStore(object):
59 class PasswordStore(object):
59 """
60 """
60 Helper object handling password save&restore. Passwords
61 Helper object handling password save&restore. Passwords
61 are saved both in local memory cache, and keyring, and are
62 are saved both in local memory cache, and keyring, and are
62 restored from those.
63 restored from those.
63 """
64 """
64 def __init__(self):
65 def __init__(self):
65 self.cache = dict()
66 self.cache = dict()
66 def get_password(self, url, username):
67 def get_password(self, url, username):
67 return keyring.get_password(KEYRING_SERVICE,
68 return keyring.get_password(KEYRING_SERVICE,
68 self._format_key(url, username))
69 self._format_key(url, username))
69 def set_password(self, url, username, password):
70 def set_password(self, url, username, password):
70 keyring.set_password(KEYRING_SERVICE,
71 keyring.set_password(KEYRING_SERVICE,
71 self._format_key(url, username),
72 self._format_key(url, username),
72 password)
73 password)
74 def clear_password(self, url, username):
75 self.set_password(url, username, "")
73 def _format_key(self, url, username):
76 def _format_key(self, url, username):
74 return "%s@@%s" % (username, url)
77 return "%s@@%s" % (username, url)
75
78
76 password_store = PasswordStore()
79 password_store = PasswordStore()
77
80
78 ############################################################
81 ############################################################
79
82
80 @monkeypatch_method(passwordmgr)
83 @monkeypatch_method(passwordmgr)
81 def find_user_password(self, realm, authuri):
84 def find_user_password(self, realm, authuri):
82 """
85 """
83 keyring-based implementation of username/password query
86 keyring-based implementation of username/password query
84
87
85 Passwords are saved in gnome keyring, OSX/Chain or other platform
88 Passwords are saved in gnome keyring, OSX/Chain or other platform
86 specific storage and keyed by the repository url
89 specific storage and keyed by the repository url
87 """
90 """
88 # Calculate the true remote url. authuri happens to contain things like
91 # Calculate the true remote url. authuri happens to contain things like
89 # https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
92 # https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
90 parsed_url = urlparse(authuri)
93 parsed_url = urlparse(authuri)
91 base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
94 base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
92
95
93 # Extracting possible username/password stored in directly in repository url
96 # Extracting possible username/password stored in directly in repository url
94 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
97 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
95
98
96 # Checking the local cache (single command may repeat the call many
99 # Checking the local cache (single command may repeat the call many
97 # times)
100 # times)
98 if not hasattr(self, '_pwd_cache'):
101 if not hasattr(self, '_pwd_cache'):
99 self._pwd_cache = {}
102 self._pwd_cache = {}
100 cache_key = (realm, base_url)
103 cache_key = (realm, base_url)
101 cached_auth = self._pwd_cache.get(cache_key)
104 cached_auth = self._pwd_cache.get(cache_key)
102 if cached_auth:
105 if cached_auth:
103 self.ui.debug("Found cached auth tokens: %s, %s\n" % (
106 self.ui.debug("Found cached auth tokens for %s: %s, %s\n" % (
104 cached_auth[0], cached_auth[1] and '********' or ''))
107 base_url, cached_auth[0], cached_auth[1] and '********' or ''))
105 return cached_auth
108 return cached_auth
106
109
107 # Loading username (and maybe password) from [auth] in local .hg/hgrc
110 # Loading username (and maybe password) from [auth] in local .hg/hgrc
108 if not user:
111 if not user:
109 # Lines below unfortunately do not work, readauthtoken
112 # Lines below unfortunately do not work, readauthtoken
110 # always return None. Why? Because
113 # always return None. Why? Because
111 # self.ui here describes the *remote* repository, so
114 # self.ui here describes the *remote* repository, so
112 # does *not* contain any option from local .hg/hgrc.
115 # does *not* contain any option from local .hg/hgrc.
113 #
116 #
114 #auth_token = self.readauthtoken(base_url)
117 #auth_token = self.readauthtoken(base_url)
115 #if auth_token:
118 #if auth_token:
116 # user, pwd = auth.get('username'), auth.get('password')
119 # user, pwd = auth.get('username'), auth.get('password')
117 #
120 #
118 # so - workaround
121 # so - workaround
119 repo_root = self.ui.config("bundle", "mainreporoot")
122 repo_root = self.ui.config("bundle", "mainreporoot")
120 if repo_root:
123 if repo_root:
121 from mercurial.ui import ui as _ui
124 from mercurial.ui import ui as _ui
122 import os
125 import os
123 local_ui = _ui(self.ui)
126 local_ui = _ui(self.ui)
124 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
127 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
125 local_passwordmgr = passwordmgr(local_ui)
128 local_passwordmgr = passwordmgr(local_ui)
126 auth_token = local_passwordmgr.readauthtoken(base_url)
129 auth_token = local_passwordmgr.readauthtoken(base_url)
127 if auth_token:
130 if auth_token:
128 user, pwd = auth_token.get('username'), auth_token.get('password')
131 user, pwd = auth_token.get('username'), auth_token.get('password')
129 self.ui.debug("Found .hg/hgrc auth tokens: %s, %s\n" % (
132 self.ui.debug("Found .hg/hgrc auth tokens: %s, %s\n" % (
130 user, pwd and '********' or ''))
133 user, pwd and '********' or ''))
131
134
132 # username still not known? Asking
135 # username still not known? Asking
136 prompted = False
133 if not user:
137 if not user:
134 if not self.ui.interactive():
138 if not self.ui.interactive():
135 raise util.Abort(_('mercurial_keyring: http authorization required'))
139 raise util.Abort(_('mercurial_keyring: http authorization required'))
136 self.ui.write(_("http authorization required\n"))
140 self.ui.write(_("http authorization required\n"))
137 self.ui.status(_("realm: %s\n") % realm)
141 self.ui.status(_("realm: %s\n") % realm)
138 user = self.ui.prompt(_("user:"), default=None)
142 user = self.ui.prompt(_("user:"), default=None)
143 prompted = True
139
144
140 # username known and still no password? Time to check keyring
145 # username known and still no password? Time to check keyring
141 if user and not pwd:
146 if user and not pwd:
142 pwd = password_store.get_password(base_url, user)
147 pwd = password_store.get_password(base_url, user)
143 if pwd:
148 if pwd:
144 self.ui.debug("Found keyring password for %s\n" % user)
149 self.ui.debug("Found keyring password for %s\n" % user)
145
150
146 # password still not known? Asking
151 # password still not known? Asking
147 if not pwd:
152 if not pwd:
148 if not self.ui.interactive():
153 if not prompted:
149 raise util.Abort(_('mercurial_keyring: http authorization required'))
154 if not self.ui.interactive():
150 pwd = self.ui.getpass(_("password: "))
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: "))
151 password_store.set_password(base_url, user, pwd)
161 password_store.set_password(base_url, user, pwd)
152 self.ui.debug("Saved keyring password for %s\n" % user)
162 self.ui.debug("Saved keyring password for %s\n" % user)
153
163
154 self._pwd_cache[cache_key] = (user, pwd)
164 self._pwd_cache[cache_key] = (user, pwd)
155
165
166 self.ui.debug("Returning auth tokens for %s: %s, %s\n" % (
167 base_url, user, pwd and '********' or ''))
156 return user, pwd
168 return user, pwd
157 #return None, None
169 #return None, None
158
170
171 ############################################################
172
173 # We patch httprespository.do_cmd to grab information that
174 # the request failed due to wrong auth - and clear the wrong
175 # password
176
177 orig_do_cmd = httprepository.do_cmd
178
179 @monkeypatch_method(httprepository)
180 def do_cmd(self, cmd, **args):
181 try:
182 orig_do_cmd(self, cmd, **args)
183 except util.Abort, e:
184 self.ui.debug("Authorization failed for %s\n" % self.url())
185 raise
General Comments 0
You need to be logged in to leave comments. Login now