##// 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 1 # -*- coding: utf-8 -*-
2 2
3 3 """
4 4 Storing HTTP authentication passwords in keyring database.
5 5
6 6 Installation method(s):
7 7
8 8 1) in ~/.hgrc (or /etc/hgext/...)
9 9
10 10 [extensions]
11 11 ...
12 12 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
13 13
14 14
15 15 2) Drop this file to hgext directory and in ~/.hgrc
16 16
17 17 [extensions]
18 18 hgext.mercurial_keyring =
19 19
20 20 """
21 21
22 22 #import mercurial.demandimport
23 23 #mercurial.demandimport.disable()
24 24
25 25 from mercurial import hg, repo, util
26 26 from mercurial.i18n import _
27 27 try:
28 28 from mercurial.url import passwordmgr
29 29 except:
30 30 from mercurial.httprepo import passwordmgr
31 from mercurial.httprepo import httprepository
31 32
32 33 import keyring
33 34 import getpass
34 35 from urlparse import urlparse
35 36 import urllib2
36 37
37 38 KEYRING_SERVICE = "Mercurial"
38 39
39 40 ############################################################
40 41
41 42 def monkeypatch_class(name, bases, namespace):
42 43 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
43 44 assert len(bases) == 1, "Exactly one base class required"
44 45 base = bases[0]
45 46 for name, value in namespace.iteritems():
46 47 if name != "__metaclass__":
47 48 setattr(base, name, value)
48 49 return base
49 50
50 51 def monkeypatch_method(cls):
51 52 def decorator(func):
52 53 setattr(cls, func.__name__, func)
53 54 return func
54 55 return decorator
55 56
56 57 ############################################################
57 58
58 59 class PasswordStore(object):
59 60 """
60 61 Helper object handling password save&restore. Passwords
61 62 are saved both in local memory cache, and keyring, and are
62 63 restored from those.
63 64 """
64 65 def __init__(self):
65 66 self.cache = dict()
66 67 def get_password(self, url, username):
67 68 return keyring.get_password(KEYRING_SERVICE,
68 69 self._format_key(url, username))
69 70 def set_password(self, url, username, password):
70 71 keyring.set_password(KEYRING_SERVICE,
71 72 self._format_key(url, username),
72 73 password)
74 def clear_password(self, url, username):
75 self.set_password(url, username, "")
73 76 def _format_key(self, url, username):
74 77 return "%s@@%s" % (username, url)
75 78
76 79 password_store = PasswordStore()
77 80
78 81 ############################################################
79 82
80 83 @monkeypatch_method(passwordmgr)
81 84 def find_user_password(self, realm, authuri):
82 85 """
83 86 keyring-based implementation of username/password query
84 87
85 88 Passwords are saved in gnome keyring, OSX/Chain or other platform
86 89 specific storage and keyed by the repository url
87 90 """
88 91 # Calculate the true remote url. authuri happens to contain things like
89 92 # https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
90 93 parsed_url = urlparse(authuri)
91 94 base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
92 95
93 96 # Extracting possible username/password stored in directly in repository url
94 97 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
95 98
96 99 # Checking the local cache (single command may repeat the call many
97 100 # times)
98 101 if not hasattr(self, '_pwd_cache'):
99 102 self._pwd_cache = {}
100 103 cache_key = (realm, base_url)
101 104 cached_auth = self._pwd_cache.get(cache_key)
102 105 if cached_auth:
103 self.ui.debug("Found cached auth tokens: %s, %s\n" % (
104 cached_auth[0], cached_auth[1] and '********' or ''))
106 self.ui.debug("Found cached auth tokens for %s: %s, %s\n" % (
107 base_url, cached_auth[0], cached_auth[1] and '********' or ''))
105 108 return cached_auth
106 109
107 110 # Loading username (and maybe password) from [auth] in local .hg/hgrc
108 111 if not user:
109 112 # Lines below unfortunately do not work, readauthtoken
110 113 # always return None. Why? Because
111 114 # self.ui here describes the *remote* repository, so
112 115 # does *not* contain any option from local .hg/hgrc.
113 116 #
114 117 #auth_token = self.readauthtoken(base_url)
115 118 #if auth_token:
116 119 # user, pwd = auth.get('username'), auth.get('password')
117 120 #
118 121 # so - workaround
119 122 repo_root = self.ui.config("bundle", "mainreporoot")
120 123 if repo_root:
121 124 from mercurial.ui import ui as _ui
122 125 import os
123 126 local_ui = _ui(self.ui)
124 127 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
125 128 local_passwordmgr = passwordmgr(local_ui)
126 129 auth_token = local_passwordmgr.readauthtoken(base_url)
127 130 if auth_token:
128 131 user, pwd = auth_token.get('username'), auth_token.get('password')
129 132 self.ui.debug("Found .hg/hgrc auth tokens: %s, %s\n" % (
130 133 user, pwd and '********' or ''))
131 134
132 135 # username still not known? Asking
136 prompted = False
133 137 if not user:
134 138 if not self.ui.interactive():
135 139 raise util.Abort(_('mercurial_keyring: http authorization required'))
136 140 self.ui.write(_("http authorization required\n"))
137 141 self.ui.status(_("realm: %s\n") % realm)
138 142 user = self.ui.prompt(_("user:"), default=None)
143 prompted = True
139 144
140 145 # username known and still no password? Time to check keyring
141 146 if user and not pwd:
142 147 pwd = password_store.get_password(base_url, user)
143 148 if pwd:
144 149 self.ui.debug("Found keyring password for %s\n" % user)
145 150
146 151 # password still not known? Asking
147 152 if not pwd:
153 if not prompted:
148 154 if not self.ui.interactive():
149 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:
150 160 pwd = self.ui.getpass(_("password: "))
151 161 password_store.set_password(base_url, user, pwd)
152 162 self.ui.debug("Saved keyring password for %s\n" % user)
153 163
154 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 168 return user, pwd
157 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