##// END OF EJS Templates
merge
Marcin Kasperski -
r51:d3a09339 merge default
parent child Browse files
Show More
@@ -0,0 +1,12 b''
1 93430018d00ee6cb78e5e72b2272ff7c5260b361 0.1.0
2 b176c215bd8c1c812ebe0fec39e7ab6acfd4cf07 0.1.1
3 62d2b5bbd611a5e203c3a0af165b01f52bcc2d71 0.2.0
4 756027411cc7f05fd8f40edc0bcfb88e28bf93a8 0.3.0
5 0f1b184b352ee73da40856cef57b5b33b9839e1d 0.3.1
6 b46cf9276f80f3e11485bf93c675a92a4ddb72d6 0.3.2
7 8224c263d919edbc49d8d3f7970bcecc0e970861 0.3.3
8 8224c263d919edbc49d8d3f7970bcecc0e970861 0.3.3
9 666f58e13c64ce54b701e3cef568c38ecf8acfec 0.3.3
10 e0c8034ca644c670b5baeceefe1ae4d00aca3936 0.4.0
11 e0c8034ca644c670b5baeceefe1ae4d00aca3936 0.4.0
12 86426d5318a8dd0c4b8df709556d068ff5027ae0 0.4.0
@@ -0,0 +1,179 b''
1 .. -*- mode: rst -*-
2
3 =================
4 mercurial_keyring
5 =================
6
7 ``mercurial_keyring`` is a Mercurial_ extension used to securely save
8 HTTP and SMTP authentication passwords in password databases (Gnome
9 Keyring, KDE KWallet, OSXKeyChain, specific solutions for Win32 and
10 command line). This extension uses and wraps services of the keyring_
11 library.
12
13 .. _keyring: http://pypi.python.org/pypi/keyring
14 .. _Mercurial: http://mercurial.selenic.com
15
16 How does it work
17 ================
18
19 The extension prompts for the password on the first pull/push (in case
20 of HTTP) or first email (in case of SMTP), just like it is done by
21 default, but saves the password. On successive runs it checks for the
22 username in ``.hg/hgrc``, then for suitable password in the password
23 database, and uses those credentials (if found).
24
25 In case password turns out to be incorrect (either because it was
26 invalid, or because it was changed on the server) or missing it just
27 prompts the user again.
28
29 Passwords are identified by the combination of username and remote
30 address, so they can be reused between repositories if they access the
31 same remote repository (or the same SMTP server).
32
33 Installation
34 ============
35
36 Prerequisites
37 -------------
38
39 Install the keyring_ library:
40
41 ::
42
43 easy_install keyring
44
45 (or ``pip keyring``). On Debian "Sid" the library can be also
46 installed from the official archive (packages ``python-keyring``
47 and either ``python-keyring-gnome`` or ``python-keyring-kwallet``).
48
49 Extension installation
50 ----------------------
51
52 There are two possible ways of installing the extension: using PyPi package,
53 or using individual file.
54
55 To install as a package use ``easy_install``:
56
57 ::
58
59 easy_install mercurial_keyring
60
61 and then enable it in ``~/.hgrc`` (or ``/etc/mercurial/hgrc``) using:
62
63 ::
64
65 [extensions]
66 mercurial_keyring =
67
68 To install using individual file, download the
69 `mercurial_keyring.py`_ file, save it anywhere you like, and
70 put the following in ``~/.hgrc`` (or ``/etc/mercurial/hgrc``):
71
72 ::
73
74 [extensions]
75 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
76
77 .. _the code:
78 .. _mercurial_keyring.py: http://bitbucket.org/Mekk/mercurial_keyring/src/tip/mercurial_keyring.py
79
80 Password backend configuration
81 ==============================
82
83 The library should usually pick the most appropriate password backend
84 without configuration. Still, if necessary, it can be configured using
85 ``~/keyringrc.cfg`` file (``keyringrc.cfg`` in the home directory of
86 the current user). Refer to keyring_ docs for more details.
87
88 *I considered handling similar options in hgrc, but decided that
89 single user may use more than one keyring-based script. Still, I am
90 open to suggestions.*
91
92 Repository configuration (HTTP)
93 ===============================
94
95 Edit repository-local ``.hg/hgrc`` and save there the remote
96 repository path and the username, but do not save the password. For
97 example:
98
99 ::
100
101 [paths]
102 myremote = https://my.server.com/hgrepo/someproject
103
104 [auth]
105 myremote.schemes = http https
106 myremote.prefix = my.server.com/hgrepo
107 myremote.username = mekk
108
109 Simpler form with url-embedded name can also be used:
110
111 ::
112
113 [paths]
114 bitbucket = https://User@bitbucket.org/User/project_name/
115
116 If prefix is specified, it is used to identify the password (so all
117 repositories with the same prefix and the same username will share the
118 same password). Otherwise full repository URL is used for this
119 purpose.
120
121 Note: if both username and password are given in ``.hg/hgrc``,
122 extension will use them without using the password database. If
123 username is not given, extension will prompt for credentials every
124 time, also without saving the password.
125
126 Repository configuration (SMTP)
127 ===============================
128
129 Edit either repository-local ``.hg/hgrc``, or ``~/.hgrc`` and set
130 there all standard email and smtp properties, including SMTP
131 username, but without SMTP password. For example:
132
133 ::
134
135 [email]
136 method = smtp
137 from = Joe Doe <Joe.Doe@remote.com>
138
139 [smtp]
140 host = smtp.gmail.com
141 port = 587
142 username = JoeDoe@gmail.com
143 tls = true
144
145 Just as in case of HTTP, you *must* set username, but *must not* set
146 password here to use the extension, in other cases it will revert to
147 the default behavior.
148
149 Usage
150 =====
151
152 Configure the repository as above, then just ``hg pull``, ``hg push``,
153 etc. You should be asked for the password only once (per every
154 username and remote repository prefix or url combination).
155
156
157 Similarly, for email, configure as above and just ``hg email``.
158 Again, you will be asked for the password once (per every username and
159 email server address combination).
160
161 Implementation details
162 ======================
163
164 The extension is monkey-patching the mercurial ``passwordmgr`` class
165 to replace the find_user_password method. Detailed order of operations
166 is described in the comments inside `the code`_.
167
168 Development
169 ===========
170
171 Development is tracked on BitBucket, see
172 http://bitbucket.org/Mekk/mercurial_keyring/
173
174
175 Additional notes
176 ================
177
178 Information about this extension is also available
179 on Mercurial Wiki: http://mercurial.selenic.com/wiki/KeyringExtension
@@ -2,4 +2,9 b' syntax: regexp'
2
2
3 \.pyc$
3 \.pyc$
4 ~$
4 ~$
5 ^\.\# No newline at end of file
5 ^\.\#
6 ^\.(project|pydevproject)$
7 ^\.settings/
8 ^build/
9 ^dist/
10 ^mercurial_keyring\.egg-info/
@@ -1,53 +1,33 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2 #
3 """
3 # mercurial_keyring: save passwords in password database
4 Storing HTTP authentication passwords in keyring database.
4 #
5
5 # Copyright 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
6 Installation method(s):
6 #
7
7 # This software may be used and distributed according to the terms
8 1) in ~/.hgrc (or /etc/hgext/...)
8 # of the GNU General Public License, incorporated herein by reference.
9
9 #
10 [extensions]
10 # See README.txt for more details.
11 ...
12 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
13
14
15 2) Drop this file to hgext directory and in ~/.hgrc
16
17 [extensions]
18 hgext.mercurial_keyring =
19
20 """
21
22 #import mercurial.demandimport
23 #mercurial.demandimport.disable()
24
11
25 from mercurial import hg, repo, util
12 from mercurial import hg, repo, util
26 from mercurial.i18n import _
13 from mercurial.i18n import _
27 try:
14 try:
28 from mercurial.url import passwordmgr
15 from mercurial.url import passwordmgr
29 except:
16 except:
30 from mercurial.httprepo import passwordmgr
17 from mercurial.httprepo import passwordmgr
31 from mercurial.httprepo import httprepository
18 from mercurial.httprepo import httprepository
19 from mercurial import mail
32
20
33 import keyring
21 import keyring
34 import getpass
35 from urlparse import urlparse
22 from urlparse import urlparse
36 import urllib2
23 import urllib2
24 import smtplib, socket
25 import os
37
26
38 KEYRING_SERVICE = "Mercurial"
27 KEYRING_SERVICE = "Mercurial"
39
28
40 ############################################################
29 ############################################################
41
30
42 def monkeypatch_class(name, bases, namespace):
43 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
44 assert len(bases) == 1, "Exactly one base class required"
45 base = bases[0]
46 for name, value in namespace.iteritems():
47 if name != "__metaclass__":
48 setattr(base, name, value)
49 return base
50
51 def monkeypatch_method(cls):
31 def monkeypatch_method(cls):
52 def decorator(func):
32 def decorator(func):
53 setattr(cls, func.__name__, func)
33 setattr(cls, func.__name__, func)
@@ -58,27 +38,41 b' def monkeypatch_method(cls):'
58
38
59 class PasswordStore(object):
39 class PasswordStore(object):
60 """
40 """
61 Helper object handling keyring usage (password save&restore).
41 Helper object handling keyring usage (password save&restore,
42 the way passwords are keyed in the keyring).
62 """
43 """
63 def __init__(self):
44 def __init__(self):
64 self.cache = dict()
45 self.cache = dict()
65 def get_password(self, url, username):
46 def get_http_password(self, url, username):
66 return keyring.get_password(KEYRING_SERVICE,
47 return keyring.get_password(KEYRING_SERVICE,
67 self._format_key(url, username))
48 self._format_http_key(url, username))
68 def set_password(self, url, username, password):
49 def set_http_password(self, url, username, password):
69 keyring.set_password(KEYRING_SERVICE,
50 keyring.set_password(KEYRING_SERVICE,
70 self._format_key(url, username),
51 self._format_http_key(url, username),
71 password)
52 password)
72 def clear_password(self, url, username):
53 def clear_http_password(self, url, username):
73 self.set_password(url, username, "")
54 self.set_http_password(url, username, "")
74 def _format_key(self, url, username):
55 def _format_http_key(self, url, username):
75 return "%s@@%s" % (username, url)
56 return "%s@@%s" % (username, url)
57 def get_smtp_password(self, machine, port, username):
58 return keyring.get_password(
59 KEYRING_SERVICE,
60 self._format_smtp_key(machine, port, username))
61 def set_smtp_password(self, machine, port, username, password):
62 keyring.set_password(
63 KEYRING_SERVICE,
64 self._format_smtp_key(machine, port, username),
65 password)
66 def clear_smtp_password(self, machine, port, username):
67 self.set_smtp_password(url, username, "")
68 def _format_smtp_key(self, machine, port, username):
69 return "%s@@%s:%s" % (username, machine, str(port))
76
70
77 password_store = PasswordStore()
71 password_store = PasswordStore()
78
72
79 ############################################################
73 ############################################################
80
74
81 class PasswordHandler(object):
75 class HTTPPasswordHandler(object):
82 """
76 """
83 Actual implementation of password handling (user prompting,
77 Actual implementation of password handling (user prompting,
84 configuration file searching, keyring save&restore).
78 configuration file searching, keyring save&restore).
@@ -88,93 +82,127 b' class PasswordHandler(object):'
88 def __init__(self):
82 def __init__(self):
89 self.pwd_cache = {}
83 self.pwd_cache = {}
90 self.last_reply = None
84 self.last_reply = None
91
85
92 def find_auth(self, pwmgr, realm, authuri):
86 def find_auth(self, pwmgr, realm, authuri):
93 """
87 """
94 Actual implementation of find_user_password
88 Actual implementation of find_user_password - different
89 ways of obtaining the username and password.
95 """
90 """
96 ui = pwmgr.ui
91 ui = pwmgr.ui
97
92
98 # If we are called again just after identical previous request,
93 # If we are called again just after identical previous
99 # then the previously returned auth must have been wrong. So we
94 # request, then the previously returned auth must have been
100 # note this to force password prompt
95 # wrong. So we note this to force password prompt (and avoid
96 # reusing bad password indifinitely).
101 after_bad_auth = (self.last_reply \
97 after_bad_auth = (self.last_reply \
102 and (self.last_reply['realm'] == realm) \
98 and (self.last_reply['realm'] == realm) \
103 and (self.last_reply['authuri'] == authuri))
99 and (self.last_reply['authuri'] == authuri))
104
100
101 # Strip arguments to get actual remote repository url.
105 base_url = self.canonical_url(authuri)
102 base_url = self.canonical_url(authuri)
106
103
107 # Extracting possible username (or password)
104 # Extracting possible username (or password)
108 # stored in directly in repository url
105 # stored directly in repository url
109 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(pwmgr, realm, authuri)
106 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
107 pwmgr, realm, authuri)
110 if user and pwd:
108 if user and pwd:
111 self._debug_reply(ui, _("Auth data found in repository URL"), base_url, user, pwd)
109 self._debug_reply(ui, _("Auth data found in repository URL"),
112 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
110 base_url, user, pwd)
113 return user, pwd
111 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
112 return user, pwd
113
114 # Loading .hg/hgrc [auth] section contents. If prefix is given,
115 # it will be used as a key to lookup password in the keyring.
116 auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url)
117 if prefix_url:
118 keyring_url = prefix_url
119 else:
120 keyring_url = base_url
121 ui.debug("keyring URL: %s\n" % keyring_url)
114
122
115 # Checking the memory cache (there may be many http calls per command)
123 # Checking the memory cache (there may be many http calls per command)
116 cache_key = (realm, base_url)
124 cache_key = (realm, keyring_url)
117 if not after_bad_auth:
125 if not after_bad_auth:
118 cached_auth = self.pwd_cache.get(cache_key)
126 cached_auth = self.pwd_cache.get(cache_key)
119 if cached_auth:
127 if cached_auth:
120 user, pwd = cached_auth
128 user, pwd = cached_auth
121 self._debug_reply(ui, _("Cached auth data found"), base_url, user, pwd)
129 self._debug_reply(ui, _("Cached auth data found"),
122 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
130 base_url, user, pwd)
123 return user, pwd
131 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
132 return user, pwd
124
133
125 # Loading username and maybe password from [auth]
134 if auth_user:
126 nuser, pwd = self.load_hgrc_auth(ui, base_url)
135 if user:
127 if nuser:
136 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, auth_user)))
128 if user:
137 user = auth_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)))
138 if pwd:
130 user = nuser
139 self.pwd_cache[cache_key] = user, pwd
131 if pwd:
140 self._debug_reply(ui, _("Auth data set in .hg/hgrc"),
132 self.pwd_cache[cache_key] = user, pwd
141 base_url, user, pwd)
133 self._debug_reply(ui, _("Auth data set in .hg/hgrc"), base_url, user, pwd)
142 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
134 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
143 return user, pwd
135 return user, pwd
144 else:
136 else:
145 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
137 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
138
146
139 # If username is known, and we are not after failure, we can try keyring
147 # Loading password from keyring.
148 # Only if username is known (so we know the key) and we are
149 # not after failure (so we don't reuse the bad password).
140 if user and not after_bad_auth:
150 if user and not after_bad_auth:
141 pwd = password_store.get_password(base_url, user)
151 pwd = password_store.get_http_password(keyring_url, user)
142 if pwd:
152 if pwd:
143 self.pwd_cache[cache_key] = user, pwd
153 self.pwd_cache[cache_key] = user, pwd
144 self._debug_reply(ui, _("Keyring password found"), base_url, user, pwd)
154 self._debug_reply(ui, _("Keyring password found"),
145 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
155 base_url, user, pwd)
146 return user, pwd
156 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
157 return user, pwd
147
158
159 # Is the username permanently set?
148 fixed_user = (user and True or False)
160 fixed_user = (user and True or False)
149
161
150 # Last resort: interactive prompt
162 # Last resort: interactive prompt
151 if not ui.interactive():
163 if not ui.interactive():
152 raise util.Abort(_('mercurial_keyring: http authorization required'))
164 raise util.Abort(_('mercurial_keyring: http authorization required but program used in non-interactive mode'))
153 ui.write(_("http authorization required\n"))
165 ui.write(_("http authorization required\n"))
154 ui.status(_("realm: %s\n") % realm)
166 ui.status(_("realm: %s\n") % realm)
155 if fixed_user:
167 if fixed_user:
156 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
168 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
157 else:
169 else:
158 user = ui.prompt(_("user:"), default=None)
170 user = ui.prompt(_("user:"), default=None)
159 pwd = ui.getpass(_("password: "))
171 pwd = ui.getpass(_("password: "))
160
172
161 if fixed_user:
173 if fixed_user:
162 # We save in keyring only if username is fixed. Otherwise we won't
174 # Saving password to the keyring.
163 # be able to find the password so it does not make any sense to
175 # It is done only if username is permanently set.
164 # preserve it
176 # Otherwise we won't be able to find the password so it
165 ui.debug("Saving password for %s to keyring\n" % user)
177 # does not make much sense to preserve it
166 password_store.set_password(base_url, user, pwd)
178 ui.debug("Saving password for %s to keyring\n" % user)
179 password_store.set_http_password(keyring_url, user, pwd)
167
180
181 # Saving password to the memory cache
168 self.pwd_cache[cache_key] = user, pwd
182 self.pwd_cache[cache_key] = user, pwd
169 self._debug_reply(ui, _("Manually entered password"), base_url, user, pwd)
183
184 self._debug_reply(ui, _("Manually entered password"),
185 base_url, user, pwd)
170 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
186 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
171 return user, pwd
187 return user, pwd
172
188
173 def load_hgrc_auth(self, ui, base_url):
189 def load_hgrc_auth(self, ui, base_url):
174 """
190 """
175 Loading username and possibly password from [auth] in local
191 Loading [auth] section contents from local .hgrc
176 repo .hgrc
192
193 Returns (username, password, prefix) tuple (every
194 element can be None)
177 """
195 """
196 # Theoretically 3 lines below should do:
197
198 #auth_token = self.readauthtoken(base_url)
199 #if auth_token:
200 # user, pwd = auth.get('username'), auth.get('password')
201
202 # Unfortunately they do not work, readauthtoken always return
203 # None. Why? Because ui (self.ui of passwordmgr) describes the
204 # *remote* repository, so does *not* contain any option from
205 # local .hg/hgrc.
178
206
179 # TODO: mercurial 1.4.2 is claimed to resolve this problem
207 # TODO: mercurial 1.4.2 is claimed to resolve this problem
180 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
208 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
@@ -182,59 +210,167 b' class PasswordHandler(object):'
182 # not be necessary. As it will take some time until people
210 # not be necessary. As it will take some time until people
183 # migrate to >= 1.4.2, it would be best to implement
211 # migrate to >= 1.4.2, it would be best to implement
184 # workaround conditionally.
212 # workaround conditionally.
185
213
186 # Lines below unfortunately do not work, readauthtoken
187 # always return None. Why? Because
188 # ui (self.ui of passwordmgr) describes the *remote* repository, so
189 # does *not* contain any option from local .hg/hgrc.
190
191 #auth_token = self.readauthtoken(base_url)
192 #if auth_token:
193 # user, pwd = auth.get('username'), auth.get('password')
194
195 # Workaround: we recreate the repository object
214 # Workaround: we recreate the repository object
196 repo_root = ui.config("bundle", "mainreporoot")
215 repo_root = ui.config("bundle", "mainreporoot")
216
217 from mercurial.ui import ui as _ui
218 local_ui = _ui(ui)
197 if repo_root:
219 if repo_root:
198 from mercurial.ui import ui as _ui
220 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
199 import os
221 local_passwordmgr = passwordmgr(local_ui)
200 local_ui = _ui(ui)
222 auth_token = local_passwordmgr.readauthtoken(base_url)
201 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
223 if auth_token:
202 local_passwordmgr = passwordmgr(local_ui)
224 username = auth_token.get('username')
203 auth_token = local_passwordmgr.readauthtoken(base_url)
225 password = auth_token.get('password')
204 if auth_token:
226 prefix = auth_token.get('prefix')
205 return auth_token.get('username'), auth_token.get('password')
227 shortest_url = self.shortest_url(base_url, prefix)
206 return None, None
228 return username, password, shortest_url
229
230 return None, None, None
207
231
232 def shortest_url(self, base_url, prefix):
233 if not prefix or prefix == '*':
234 return base_url
235 scheme, hostpath = base_url.split('://', 1)
236 p = prefix.split('://', 1)
237 if len(p) > 1:
238 prefix_host_path = p[1]
239 else:
240 prefix_host_path = prefix
241 shortest_url = scheme + '://' + prefix_host_path
242 return shortest_url
208
243
209 def canonical_url(self, authuri):
244 def canonical_url(self, authuri):
210 """
245 """
211 Strips query params from url. Used to convert
246 Strips query params from url. Used to convert urls like
212 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
247 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
213 to
248 to
214 https://repo.machine.com/repos/apps/module
249 https://repo.machine.com/repos/apps/module
215 """
250 """
216 parsed_url = urlparse(authuri)
251 parsed_url = urlparse(authuri)
217 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
252 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
253 parsed_url.path)
218
254
219 def _debug_reply(self, ui, msg, url, user, pwd):
255 def _debug_reply(self, ui, msg, url, user, pwd):
220 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (msg, url, user, pwd and '*' * len(pwd) or 'not set'))
256 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (
257 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
221
258
222 ############################################################
259 ############################################################
223
260
224 # The idea: if we are re-asked with exactly the same params
225 # (authuri, not base_url) then password must have been wrong.
226
227 @monkeypatch_method(passwordmgr)
261 @monkeypatch_method(passwordmgr)
228 def find_user_password(self, realm, authuri):
262 def find_user_password(self, realm, authuri):
229 """
263 """
230 keyring-based implementation of username/password query
264 keyring-based implementation of username/password query
265 for HTTP(S) connections
231
266
232 Passwords are saved in gnome keyring, OSX/Chain or other platform
267 Passwords are saved in gnome keyring, OSX/Chain or other platform
233 specific storage and keyed by the repository url
268 specific storage and keyed by the repository url
234 """
269 """
235 # Extend object attributes
270 # Extend object attributes
236 if not hasattr(self, '_pwd_handler'):
271 if not hasattr(self, '_pwd_handler'):
237 self._pwd_handler = PasswordHandler()
272 self._pwd_handler = HTTPPasswordHandler()
238
273
239 return self._pwd_handler.find_auth(self, realm, authuri)
274 return self._pwd_handler.find_auth(self, realm, authuri)
240
275
276 ############################################################
277
278 def try_smtp_login(ui, smtp_obj, username, password):
279 """
280 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
281 password.
282
283 Returns:
284 - True if login succeeded
285 - False if login failed due to the wrong credentials
286
287 Throws Abort exception if login failed for any other reason.
288
289 Immediately returns False if password is empty
290 """
291 if not password:
292 return False
293 try:
294 ui.note(_('(authenticating to mail server as %s)\n') %
295 (username))
296 smtp_obj.login(username, password)
297 return True
298 except smtplib.SMTPException, inst:
299 if inst.smtp_code == 535:
300 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
301 return False
302 else:
303 raise util.Abort(inst)
304
305 def keyring_supported_smtp(ui, username):
306 """
307 keyring-integrated replacement for mercurial.mail._smtp
308 Used only when configuration file contains username, but
309 does not contain the password.
310
311 Most of the routine below is copied as-is from
312 mercurial.mail._smtp. The only changed part is
313 marked with #>>>>> and #<<<<< markers
314 """
315 local_hostname = ui.config('smtp', 'local_hostname')
316 s = smtplib.SMTP(local_hostname=local_hostname)
317 mailhost = ui.config('smtp', 'host')
318 if not mailhost:
319 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
320 mailport = int(ui.config('smtp', 'port', 25))
321 ui.note(_('sending mail: smtp host %s, port %s\n') %
322 (mailhost, mailport))
323 s.connect(host=mailhost, port=mailport)
324 if ui.configbool('smtp', 'tls'):
325 if not hasattr(socket, 'ssl'):
326 raise util.Abort(_("can't use TLS: Python SSL support "
327 "not installed"))
328 ui.note(_('(using tls)\n'))
329 s.ehlo()
330 s.starttls()
331 s.ehlo()
332
333 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
334 stored = password = password_store.get_smtp_password(
335 mailhost, mailport, username)
336 # No need to check whether password was found as try_smtp_login
337 # just returns False if it is absent.
338 while not try_smtp_login(ui, s, username, password):
339 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
340
341 if stored != password:
342 password_store.set_smtp_password(
343 mailhost, mailport, username, password)
344 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
345
346 def send(sender, recipients, msg):
347 try:
348 return s.sendmail(sender, recipients, msg)
349 except smtplib.SMTPRecipientsRefused, inst:
350 recipients = [r[1] for r in inst.recipients.values()]
351 raise util.Abort('\n' + '\n'.join(recipients))
352 except smtplib.SMTPException, inst:
353 raise util.Abort(inst)
354
355 return send
356
357 ############################################################
358
359 orig_smtp = mail._smtp
360
361 @monkeypatch_method(mail)
362 def _smtp(ui):
363 """
364 build an smtp connection and return a function to send email
365
366 This is the monkeypatched version of _smtp(ui) function from
367 mercurial/mail.py. It calls the original unless username
368 without password is given in the configuration.
369 """
370 username = ui.config('smtp', 'username')
371 password = ui.config('smtp', 'password')
372
373 if username and not password:
374 return keyring_supported_smtp(ui, username)
375 else:
376 return orig_smtp(ui)
@@ -1,3 +1,5 b''
1 version = '0.4.0'
2
1 try:
3 try:
2 from setuptools import setup, find_packages
4 from setuptools import setup, find_packages
3 except ImportError:
5 except ImportError:
@@ -5,28 +7,29 b' except ImportError:'
5 use_setuptools()
7 use_setuptools()
6 from setuptools import setup, find_packages
8 from setuptools import setup, find_packages
7
9
10 long_description = open("README.txt").read()
11
8 setup(
12 setup(
9 name = "mercurial_keyring",
13 name = "mercurial_keyring",
10 version = '0.1.0',
14 version = version,
11 author = 'Marcin Kasperski',
15 author = 'Marcin Kasperski',
12 author_email = 'Marcin.Kasperski@mekk.waw.pl',
16 author_email = 'Marcin.Kasperski@mekk.waw.pl',
13 url = 'http://mekk.waw.pl',
17 url = 'http://bitbucket.org/Mekk/mercurial_keyring',
14 description = 'Mercurial Keyring Extension',
18 description = 'Mercurial Keyring Extension',
15 long_description = '''mercurial_keyring preserves passwords via keyring (http://pypi.python.org/pypi/keyring) library, using OSX/Keychain, KDE KWallet, Gnome Keyring, or internally supported storage (also on Win32).''',
19 long_description = long_description,
16 license = 'BSD',
20 license = 'GPL',
17 py_modules = ['mercurial_keyring'],
21 py_modules = ['mercurial_keyring'],
18 keywords = "mercurial hg keyring password",
22 keywords = "mercurial hg keyring password",
19 classifiers = [
23 classifiers = [
20 'Development Status :: 4 - Beta',
24 'Development Status :: 4 - Beta',
21 'Environment :: Any',
25 'Environment :: Console',
22 'Intended Audience :: Developers',
26 'Intended Audience :: Developers',
23 'License :: OSI Approved :: BSD License',
27 'License :: OSI Approved :: BSD License',
24 'Operating System :: Any',
28 'Operating System :: OS Independent',
25 'Topic :: Software Development :: Libraries',
29 'Topic :: Software Development :: Libraries',
26 'Topic :: Software Development :: Libraries :: Python Modules',
30 'Topic :: Software Development :: Libraries :: Python Modules',
27 'Topic :: Software Development :: Version Control'
31 'Topic :: Software Development :: Version Control'
28 ],
32 ],
29
30 install_requires = ['keyring'],
33 install_requires = ['keyring'],
31 zip_safe = True,
34 zip_safe = True,
32 )
35 )
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now