##// END OF EJS Templates
Patching various links bitbucket → heptapod
Marcin Kasperski -
r283:e95e300e default
parent child Browse files
Show More
@@ -1,422 +1,431 b''
1 1 .. -*- mode: rst; compile-command: "rst2html README.rst README.html" -*-
2 2
3 3 =======================================================
4 4 Mercurial Keyring
5 5 =======================================================
6 6
7 7 Mercurial Keyring is a Mercurial_ extension used to securely save HTTP
8 8 and SMTP authentication passwords in password databases (Gnome
9 9 Keyring, KDE KWallet, OSXKeyChain, Windows Vault etc).
10 10
11 11 With ``mercurial_keyring`` active, Mercurial remembers your passwords
12 12 and reuses them without prompting (as if you stored them in ``.hgrc``),
13 13 but password storage is reasonably secure.
14 14
15 15 Actual password storage is implemented by the keyring_ library, this
16 16 extension glues it to Mercurial.
17 17
18 18 .. contents::
19 19 :local:
20 20 :depth: 2
21 21
22 22 .. sectnum::
23 23
24 24 .. _keyring: http://pypi.python.org/pypi/keyring
25 25 .. _Mercurial: http://mercurial.selenic.com
26 26
27 27 How does it work
28 28 =======================================================
29 29
30 30 On your first pull or push to HTTP url (or first email sent via given
31 31 SMTP server), you are prompted for the password, just like bare
32 32 Mercurial does. But the password you entered is saved to appropriate
33 33 password database. On successive runs, whenever the password is
34 34 needed, ``mercurial_keyring`` checks for password in password
35 35 database, and uses it without troubling you.
36 36
37 37 In case password turns out to be incorrect (for example, because you
38 38 changed it, or entered it incorrectly), ``mercurial_keyring`` prompts
39 39 you again, and overwrites the password.
40 40
41 41 You can use many passwords (for various remote urls). Saved passwords
42 42 are identified by pair of username and url prefix. See below for
43 43 information how to configure those properly.
44 44
45 45 Installation
46 46 =======================================================
47 47
48 48 Prerequisites
49 49 -------------
50 50
51 51 This extension requires keyring_ and `mercurial_extension_utils`_ to
52 52 work. In many cases both will be installed automatically while you
53 53 install ``mercurial_keyring``, but you may need to control the process.
54 54
55 55 The keyring_ library can usually be installed by::
56 56
57 57 pip install --user keyring
58 58
59 59 (or ``easy_install keyring``), but on some systems it is preferable to
60 60 use official distribution archive. For example, on Debian and Ubuntu,
61 61 you may install ``python-keyring`` and either ``python-keyring-gnome``
62 62 or ``python-keyring-kwallet`` packages::
63 63
64 64 sudo apt-get install python-keyring python-keyring-gnome
65 65
66 66 (this will save you the need to provide working compiler and various
67 67 development libraries).
68 68
69 69 The `mercurial_extension_utils`_ module is tiny Python-only module,
70 70 which can be installed by::
71 71
72 72 pip install --user mercurial_extension_utils
73 73
74 74 but in some cases (Windows…) requires more care. See
75 75 `mercurial_extension_utils`_ documentation.
76 76
77 77
78 78 Extension installation
79 79 ----------------------
80 80
81 81 There are two possible ways of installing the extension: using PyPi package,
82 82 or using source clone.
83 83
84 84 To install as a package::
85 85
86 86 pip install --user mercurial_keyring
87 87
88 88 (or ``sudo pip install mercurial_keyring`` for system-wide
89 89 installation) and then enable it in ``~/.hgrc`` (or
90 90 ``/etc/mercurial/hgrc`` or ``Mercurial.ini``) using::
91 91
92 92 [extensions]
93 93 mercurial_keyring =
94 94
95 95 To install using source clone, install keyring_ according to the
96 96 instructions above, then clone::
97 97
98 hg clone https://bitbucket.org/Mekk/mercurial_keyring/
99 hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/
98 hg clone https://foss.heptapod.net/mercurial/mercurial_keyring/
99 hg clone https://foss.heptapod.net/mercurial/mercurial-extension_utils/
100 100
101 101 and configure Mercurial using full path to the extension module::
102 102
103 103 [extensions]
104 104 mercurial_keyring = /path/to/mercurial_keyring/mercurial_keyring.py
105 105
106 106 .. _the code:
107 .. _mercurial_keyring.py: http://bitbucket.org/Mekk/mercurial_keyring/src/tip/mercurial_keyring.py
107 .. _mercurial_keyring.py: https://foss.heptapod.net/mercurial/mercurial_keyring/src/tip/mercurial_keyring.py
108 108
109 109 Password backend configuration
110 110 =======================================================
111 111
112 112 The most appropriate password backend should usually be picked without
113 113 configuration (considering installed libraries, operating system,
114 114 active desktop session). Still, if necessary, it can be configured
115 115 using ``keyringrc.cfg`` file. Refer to keyring_ docs for more
116 116 details.
117 117
118 118 .. note::
119 119
120 120 With current (as I write) keyring (5.6), this file is (on Linux)
121 121 located at ``~/.local/share/python_keyring/keyringrc.cfg`` and
122 122 it's example content looks like::
123 123
124 124 [backend]
125 125 default-keyring=keyring.backends.Gnome.Keyring
126 126 # default-keyring=keyring.backends.kwallet.Keyring
127 127
128 128 For list of known backends run ``pydoc keyring.backends`` or
129 129 ``keyring --list-backends`` (which of those commands work,
130 130 depends on the keyring_ version).
131 131
132 132
133 133 ``hgrc`` configuration (HTTP)
134 134 =======================================================
135 135
136 136 Mercurial Keyring uses standard Mercurial ``[auth]`` configuration to
137 137 detect your username (on given remote) and url prefix. You are
138 138 strongly advised to configure both.
139 139
140 140 Without the username ``mercurial_keyring`` can't save or restore
141 141 passwords, so it disables itself.
142 142
143 143 Without url prefix ``mercurial_keyring`` works, but binds passwords to
144 144 repository urls. That means you will have to (re)enter password for
145 145 every repository cloned from given remote (and that there will be many
146 146 copies of this password in secure storage).
147 147
148 148 Repository level configuration
149 149 ------------------------------------
150 150
151 151 Edit repository-local ``.hg/hgrc`` and save there the remote
152 152 repository path and the username, but do not save the password. For
153 153 example:
154 154
155 155 ::
156 156
157 157 [paths]
158 158 myremote = https://my.server.com/hgrepo/someproject
159 159
160 160 [auth]
161 161 myremote.prefix = https://my.server.com/hgrepo
162 162 myremote.username = John
163 163
164 164 Simpler form with url-embedded name can also be used:
165 165
166 166 ::
167 167
168 168 [paths]
169 169 bitbucket = https://John@my.server.com/hgrepo/someproject/
170 170
171 171 but is not recommended.
172 172
173 173 Note that all repositories sharing the same ``prefix`` share the same
174 174 password.
175 175
176 176 Mercurial allows also for password in ``.hg/hgrc`` (either given by
177 177 ``«prefix».password``, or embedded in url). If such password is found,
178 178 Mercurial Keyring disables itself.
179 179
180 180
181 181 Account-level configuration
182 182 ---------------------------
183 183
184 184 If you are consistent about remote repository nicknames, you can
185 185 configure the username in your `~/.hgrc` (`.hgrc` in your home
186 186 directory). For example, write there::
187 187
188 188 [auth]
189 189 acme.prefix = hg.acme.com/repositories
190 190 acme.username = johnny
191 191 acme.schemes = http https
192 192 bitbucket.prefix = https://bitbucket.org
193 193 bitbucket.username = Mekk
194 194 mydep.prefix = https://dev.acmeorg.com
195 195 mydep.username = drmartin
196 196
197 197 and as long as you use ``acme`` alias for repositories like
198 198 ``https://hg.acme.com/repositories/my_beautiful_app``, username
199 199 ``johnny`` will be used, and the same password reused. Similarly
200 200 any ``hg push bitbucket`` will share the same password.
201 201
202 202 With such config repository-level ``.hg/hgrc`` need only contain
203 203 ``[paths]``.
204 204
205 205 Additional advantage of this method is that it works also during
206 206 `clone`.
207 207
208 208
209 209 .. note::
210 210
211 211 Mercurial Keyring works well with `Path Pattern`_. On my setup I use
212 212 prefix as above, and::
213 213
214 214 [path_pattern]
215 215 bitbucket.local = ~/devel/{below}
216 216 bitbucket.remote = https://bitbucket.org/Mekk/{below:/=-}
217 217
218 218 so all my repositories understand ``hg push bitbucket`` without
219 219 any repository-level configuration.
220 220
221 221
222 222 ``hgrc`` configuration (SMTP)
223 223 =======================================================
224 224
225 225 Edit either repository-local ``.hg/hgrc``, or ``~/.hgrc`` and set
226 226 there all standard email and smtp properties, including SMTP
227 227 username, but without SMTP password. For example:
228 228
229 229 ::
230 230
231 231 [email]
232 232 method = smtp
233 233 from = Joe Doe <Joe.Doe@remote.com>
234 234
235 235 [smtp]
236 236 host = smtp.gmail.com
237 237 port = 587
238 238 username = JoeDoe@gmail.com
239 239 tls = true
240 240
241 241 Just as in case of HTTP, you *must* set username, but *must not* set
242 242 password here to use the extension, in other cases it will revert to
243 243 the default behavior.
244 244
245 245 Usage
246 246 ======================================================
247 247
248 248 Saving and restoring passwords
249 249 -------------------------------------------------------
250 250
251 251 Configure the repository as above, then just ``hg pull``, ``hg push``,
252 252 etc. You should be asked for the password only once (per every
253 253 username and remote repository prefix or url combination).
254 254
255 255 Similarly, for email, configure as above and just ``hg email``.
256 256 Again, you will be asked for the password once (per every username and
257 257 email server address combination).
258 258
259 259 Checking password status (``hg keyring_check``)
260 260 -------------------------------------------------------
261 261
262 262 The ``keyring_check`` command can be used to check whether/which
263 263 password(s) are saved. It can be used in three ways:
264 264
265 265 - without parameters, it prints info related to all HTTP paths
266 266 defined for current repository (everything from ``hg paths``
267 267 that resolves to HTTP url)::
268 268
269 269 hg keyring_check
270 270
271 271 - given alias as param, it prints info about this alias::
272 272
273 273 hg keyring_check work
274 274
275 275 - finally, any path can be checked::
276 276
277 hg keyring_check https://bitbucket.org/Mekk/mercurial_keyring
277 hg keyring_check https://foss.heptapod.net/mercurial/mercurial_keyring
278 278
279 279 Deleting saved password (``hg keyring_clear``)
280 280 -------------------------------------------------------
281 281
282 282 The ``keyring_clear`` command removes saved password related to given
283 283 path. It can be used in two ways:
284 284
285 285 - given alias as param, it drops password used by this alias::
286 286
287 287 hg keyring_clear work
288 288
289 289 - given full path, it drops password related to this path::
290 290
291 hg keyring_clear https://bitbucket.org/Mekk/mercurial_keyring
291 hg keyring_clear https://foss.heptapod.net/mercurial/mercurial_keyring
292 292
293 293 Managing passwords using GUI tools
294 294 ------------------------------------------------------
295 295
296 296 Many password backends provide GUI tools for password management,
297 297 for example Gnome Keyring passwords can be managed using ``seahorse``,
298 298 and KDE Wallet using ``kwalletmanager``. Those GUI tools can be used
299 299 to review, edit, or delete saved passwords.
300 300
301 301 Unfortunately, as I write, keyring_ library does not allow one to
302 302 configure how/where exactly saved passwords are put in the hierarchy,
303 303 and the place is not always intuitive. For example, in KDE Wallet, all
304 304 passwords saved using ``mercurial_keyring`` show up in the folder
305 305 named ``Python``.
306 306
307 307 .. note::
308 308
309 309 This is slightly problematic in case ``mercurial_keyring`` is not
310 310 the only program using keyring_ library. Passwords saved by another
311 311 Python application or script (which also uses keyring_) will be put
312 312 into the same place, and it may be unclear which password belongs
313 313 to which program. To remedy this, ``mercurial_keyring`` applies
314 314 slightly unusual labels of the form
315 315 ``«username»@@«urlprefix»@Mercurial`` - for example my bitbucket
316 316 password is labelled ``Mekk@@https://bitbucket.org@Mercurial``.
317 317
318 318 Implementation details
319 319 =======================================================
320 320
321 321 The extension is monkey-patching the mercurial ``passwordmgr`` class
322 322 to replace the ``find_user_password`` method. Detailed order of operations
323 323 is described in the comments inside `the code`_.
324 324
325 325 Frequent problems
326 326 =======================================================
327 327
328 328 Most problems people face while using ``mercurial_keyring`` are in
329 329 fact problems with ``keyring`` library and it's backends. In
330 330 particular, those can manifest by:
331 331
332 332 - technical errors mentioning sentences like ``No recommended backend
333 333 was available. Install the keyrings.alt package…`` (or similar),
334 334
335 335 - warnings like ``keyring: keyring backend doesn't seem to work…``
336 336
337 337 - password prompts on every action (= passwords not being saved).
338 338
339 339 Those almost always mean that *natural* keyring backend for given
340 340 desktop type doesn't work, or is not present at all. For example,
341 341 some necessary runtime component can be down (say, you use Linux, but
342 342 neither Gnome Keyring, nor KDE Wallet, is running). Or appropriate
343 343 backend is not installed because it could not be build during keyring_
344 344 library installation (maybe because some required library was not
345 345 present at the moment of keyring installation, or maybe because
346 346 compiler as such is not present on the system).
347 347
348 348 To diagnose such problems, try using ``keyring`` utility, as described
349 349 on keyring_ documentation page, for example by::
350 350
351 351 keyring --list-backends
352 352 keyring -b keyrings.alt.Gnome.Keyring set testsvc testuser
353 353 keyring -b keyrings.alt.Gnome.Keyring get testsvc testuser
354 354
355 355 (of course using appropriate backend). If you miss the ``keyring`` command
356 356 as such, try ``python -m keyring`` instead::
357 357
358 358 python -m keyring --list-backends
359 359 python -m keyring -b keyrings.alt.Gnome.Keyring set testsvc testuser
360 360 python -m keyring -b keyrings.alt.Gnome.Keyring get testsvc testuser
361 361
362 362 If appropriate backend is missing (not listed), or doesn't work
363 363 (second or third command fails), your keyring is broken. Try looking
364 364 for further pointers in keyring_ documentation, that project mailing
365 365 list, or issue tracker. Typically it will turn out, that you need to
366 366 install some missing tool, or library, and reinstall keyring.
367 367
368 368 .. note::
369 369
370 370 Depending on keyring_ version, installation of some dependency may
371 371 resolve problem. For example (as of late 2018), I got KDE Wallet
372 372 backend working with pip-installed keyring after::
373 373
374 374 pip install dbus-python
375 375
376 376 Note also, that recent versions of keyring library (since version 12) use Python
377 377 entrypoints to find available backends. Those are incompatible with
378 378 some binary packaging methods (like ``py2app``) and may cause
379 379 problems. In particular there were packaged installations of TortoiseHG
380 which were unable to load keyring backends. See `#61 <https://bitbucket.org/Mekk/mercurial_keyring/issues/61/tortoisehg-encounters-unknown-exception>`_ for some more details.
380 which were unable to load keyring backends. See `#61 <https://foss.heptapod.net/mercurial/mercurial_keyring/issues/61/tortoisehg-encounters-unknown-exception>`_ for some more details.
381 381
382 382
383 383 If ``keyring`` command works, but mercurial with mercurial_keyring does not,
384 384 try enforcing proper backend (by means of ``keyringrc.cfg``, see above).
385 385 Only if this doesn't help, there may be a bug in mercurial_keyring.
386 386
387 387 By far easiest way to have properly working keyring is to use packaged
388 388 binary version (like ``python-keyring`` Ubuntu package, or keyring
389 389 bundled with TortoiseHG on some systems). If you pip-installed keyring
390 390 and it doesn't work, you may consider removing it via ``pip uninstall
391 391 keyring`` and looking for binary package instead.
392 392
393 393
394 394
395 395
396 396 History
397 397 =======================================================
398 398
399 399 See `HISTORY.rst`_.
400 400
401 Development
402 =======================================================
401 Repository, bug reports, enhancement suggestions
402 ===================================================
403
404 Development is tracked on HeptaPod, see
405 https://foss.heptapod.net/mercurial/mercurial_keyring/
403 406
404 Development is tracked on BitBucket, see
405 http://bitbucket.org/Mekk/mercurial_keyring/
407 Use issue tracker there for bug reports and enhancement
408 suggestions.
409
410 Thanks to Octobus_ and `Clever Cloud`_ for hosting this service.
411
406 412
407 413
408 414 Additional notes
409 415 =======================================================
410 416
411 417 Information about this extension is also available
412 418 on Mercurial Wiki: http://mercurial.selenic.com/wiki/KeyringExtension
413 419
414 420 Check also `other Mercurial extensions I wrote`_.
415 421
422 .. _Octobus: https://octobus.net/
423 .. _Clever Cloud: https://www.clever-cloud.com/
424
416 425 .. _other Mercurial extensions I wrote: http://mekk.bitbucket.io/mercurial.html
417 426
418 .. _HISTORY.rst: http://bitbucket.org/Mekk/mercurial_keyring/src/tip/HISTORY.rst
427 .. _HISTORY.rst: https://foss.heptapod.net/mercurial/mercurial_keyring/src/tip/HISTORY.rst
419 428 .. _TortoiseHg: http://tortoisehg.bitbucket.org/
420 429 .. _Mercurial: http://mercurial.selenic.com
421 .. _mercurial_extension_utils: https://bitbucket.org/Mekk/mercurial-extension_utils/
422 .. _Path Pattern: https://bitbucket.org/Mekk/mercurial-path_pattern/
430 .. _mercurial_extension_utils: https://foss.heptapod.net/mercurial/mercurial-extension_utils/
431 .. _Path Pattern: https://foss.heptapod.net/mercurial/mercurial-path_pattern/
@@ -1,887 +1,887 b''
1 1 # -*- coding: utf-8 -*-
2 2 #
3 3 # mercurial_keyring: save passwords in password database
4 4 #
5 5 # Copyright (c) 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
6 6 # All rights reserved.
7 7 #
8 8 # Redistribution and use in source and binary forms, with or without
9 9 # modification, are permitted provided that the following conditions
10 10 # are met:
11 11 # 1. Redistributions of source code must retain the above copyright
12 12 # notice, this list of conditions and the following disclaimer.
13 13 # 2. Redistributions in binary form must reproduce the above copyright
14 14 # notice, this list of conditions and the following disclaimer in the
15 15 # documentation and/or other materials provided with the distribution.
16 16 # 3. The name of the author may not be used to endorse or promote products
17 17 # derived from this software without specific prior written permission.
18 18 #
19 19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 29 #
30 30 # See README.rst for more details.
31 31
32 32 '''securely save HTTP and SMTP passwords to encrypted storage
33 33
34 34 mercurial_keyring securely saves HTTP and SMTP passwords in password
35 35 databases (Gnome Keyring, KDE KWallet, OSXKeyChain, Win32 crypto
36 36 services).
37 37
38 38 The process is automatic. Whenever bare Mercurial just prompts for
39 39 the password, Mercurial with mercurial_keyring enabled checks whether
40 40 saved password is available first. If so, it is used. If not, you
41 41 will be prompted for the password, but entered password will be
42 42 saved for the future use.
43 43
44 44 In case saved password turns out to be invalid (HTTP or SMTP login
45 45 fails) it is dropped, and you are asked for current password.
46 46
47 47 Actual password storage is implemented by Python keyring library, this
48 48 extension glues those services to Mercurial. Consult keyring
49 49 documentation for information how to configure actual password
50 50 backend (by default keyring guesses, usually correctly, for example
51 51 you get KDE Wallet under KDE, and Gnome Keyring under Gnome or Unity).
52 52 '''
53 53
54 54 import socket
55 55 import os
56 56 import sys
57 57 import re
58 58 if sys.version_info[0] < 3:
59 59 from urllib2 import (
60 60 HTTPPasswordMgrWithDefaultRealm, AbstractBasicAuthHandler, AbstractDigestAuthHandler)
61 61 else:
62 62 from urllib.request import (
63 63 HTTPPasswordMgrWithDefaultRealm, AbstractBasicAuthHandler, AbstractDigestAuthHandler)
64 64 import smtplib
65 65
66 66 from mercurial import util, sslutil, error
67 67 from mercurial.i18n import _
68 68 from mercurial.url import passwordmgr
69 69 from mercurial import mail
70 70 from mercurial.mail import SMTPS, STARTTLS
71 71 from mercurial import encoding
72 72 from mercurial import ui as uimod
73 73
74 74 # pylint: disable=invalid-name, line-too-long, protected-access, too-many-arguments
75 75
76 76 ###########################################################################
77 77 # Specific import trickery
78 78 ###########################################################################
79 79
80 80
81 81 def import_meu():
82 82 """
83 83 Convoluted import of mercurial_extension_utils, which helps
84 84 TortoiseHg/Win setups. This routine and it's use below
85 85 performs equivalent of
86 86 from mercurial_extension_utils import monkeypatch_method
87 87 but looks for some non-path directories.
88 88 """
89 89 try:
90 90 import mercurial_extension_utils
91 91 except ImportError:
92 92 my_dir = os.path.dirname(__file__)
93 93 sys.path.extend([
94 94 # In the same dir (manual or site-packages after pip)
95 95 my_dir,
96 96 # Developer clone
97 97 os.path.join(os.path.dirname(my_dir), "extension_utils"),
98 98 # Side clone
99 99 os.path.join(os.path.dirname(my_dir), "mercurial-extension_utils"),
100 100 ])
101 101 try:
102 102 import mercurial_extension_utils
103 103 except ImportError:
104 104 raise error.Abort(_("""Can not import mercurial_extension_utils.
105 105 Please install this module in Python path.
106 See Installation chapter in https://bitbucket.org/Mekk/mercurial_keyring/ for details
106 See Installation chapter in https://foss.heptapod.net/mercurial/mercurial_keyring/ for details
107 107 (and for info about TortoiseHG on Windows, or other bundled Python)."""))
108 108 return mercurial_extension_utils
109 109
110 110
111 111 meu = import_meu()
112 112 monkeypatch_method = meu.monkeypatch_method
113 113
114 114
115 115 def import_keyring():
116 116 """
117 117 Importing keyring happens to be costly if wallet is slow, so we delay it
118 118 until it is really needed. The routine below also works around various
119 119 demandimport-related problems.
120 120 """
121 121 # mercurial.demandimport incompatibility workaround.
122 122 # various keyring backends fail as they can't properly import helper
123 123 # modules (as demandimport modifies python import behaviour).
124 124 # If you get import errors with demandimport in backtrace, try
125 125 # guessing what to block and extending the list below.
126 126 mod, was_imported_now = meu.direct_import_ext(
127 127 "keyring", [
128 128 "gobject._gobject",
129 129 "configparser",
130 130 "json",
131 131 "abc",
132 132 "io",
133 133 "keyring",
134 134 "gdata.docs.service",
135 135 "gdata.service",
136 136 "types",
137 137 "atom.http",
138 138 "atom.http_interface",
139 139 "atom.service",
140 140 "atom.token_store",
141 141 "ctypes",
142 142 "secretstorage.exceptions",
143 143 "fs.opener",
144 144 "win32ctypes.pywin32",
145 145 "win32ctypes.pywin32.pywintypes",
146 146 "win32ctypes.pywin32.win32cred",
147 147 "pywintypes",
148 148 "win32cred",
149 149 ])
150 150 if was_imported_now:
151 151 # Shut up warning about uninitialized logging by keyring
152 152 meu.disable_logging("keyring")
153 153 return mod
154 154
155 155
156 156 #################################################################
157 157 # Actual implementation
158 158 #################################################################
159 159
160 160 KEYRING_SERVICE = "Mercurial"
161 161
162 162
163 163 class PasswordStore(object):
164 164 """
165 165 Helper object handling keyring usage (password save&restore,
166 166 the way passwords are keyed in the keyring).
167 167 """
168 168 def __init__(self):
169 169 self.cache = dict()
170 170
171 171 def get_http_password(self, url, username):
172 172 """
173 173 Checks whether password of username for url is available,
174 174 returns it or None
175 175 """
176 176 return self._read_password_from_keyring(
177 177 self._format_http_key(url, username))
178 178
179 179 def set_http_password(self, url, username, password):
180 180 """Saves password to keyring"""
181 181 self._save_password_to_keyring(
182 182 self._format_http_key(url, username),
183 183 password)
184 184
185 185 def clear_http_password(self, url, username):
186 186 """Drops saved password"""
187 187 self.set_http_password(url, username, b"")
188 188
189 189 @staticmethod
190 190 def _format_http_key(url, username):
191 191 """Construct actual key for password identification"""
192 192 # keyring expects str, mercurial feeds as here mostly with bytes
193 193 key = "%s@@%s" % (meu.pycompat.sysstr(username),
194 194 meu.pycompat.sysstr(url))
195 195 return key
196 196
197 197 @staticmethod
198 198 def _format_smtp_key(machine, port, username):
199 199 """Construct key for SMTP password identification"""
200 200 key = "%s@@%s:%s" % (meu.pycompat.sysstr(username),
201 201 meu.pycompat.sysstr(machine),
202 202 str(port))
203 203 return key
204 204
205 205 def get_smtp_password(self, machine, port, username):
206 206 """Checks for SMTP password in keyring, returns
207 207 password or None"""
208 208 return self._read_password_from_keyring(
209 209 self._format_smtp_key(machine, port, username))
210 210
211 211 def set_smtp_password(self, machine, port, username, password):
212 212 """Saves SMTP password to keyring"""
213 213 self._save_password_to_keyring(
214 214 self._format_smtp_key(machine, port, username),
215 215 password)
216 216
217 217 def clear_smtp_password(self, machine, port, username):
218 218 """Drops saved SMTP password"""
219 219 self.set_smtp_password(machine, port, username, "")
220 220
221 221 @staticmethod
222 222 def _read_password_from_keyring(pwdkey):
223 223 """Physically read from keyring"""
224 224 keyring = import_keyring()
225 225 try:
226 226 password = keyring.get_password(KEYRING_SERVICE, pwdkey)
227 227 except Exception as err:
228 228 ui = uimod.ui()
229 229 ui.warn(meu.ui_string(
230 230 "keyring: keyring backend doesn't seem to work, password can not be restored. Falling back to prompts. Error details: %s\n",
231 231 err))
232 232 return b''
233 233 # Reverse recoding from next routine
234 234 if isinstance(password, meu.pycompat.unicode):
235 235 return encoding.tolocal(password.encode('utf-8'))
236 236 return password
237 237
238 238 @staticmethod
239 239 def _save_password_to_keyring(pwdkey, password):
240 240 """Physically write to keyring"""
241 241 keyring = import_keyring()
242 242 # keyring in general expects unicode.
243 243 # Mercurial provides "local" encoding. See #33.
244 244 # py3 adds even more fun as we get already unicode from getpass.
245 245 # To handle those quirks, let go through encoding.fromlocal in case we
246 246 # got bytestr here, this will handle both normal py2 and emergency py3 cases.
247 247 if isinstance(password, bytes):
248 248 password = encoding.fromlocal(password).decode('utf-8')
249 249 try:
250 250 keyring.set_password(
251 251 KEYRING_SERVICE, pwdkey, password)
252 252 except Exception as err:
253 253 ui = uimod.ui()
254 254 ui.warn(meu.ui_string(
255 255 "keyring: keyring backend doesn't seem to work, password was not saved. Error details: %s\n",
256 256 err))
257 257
258 258
259 259 password_store = PasswordStore()
260 260
261 261
262 262 ############################################################
263 263 # Various utils
264 264 ############################################################
265 265
266 266 class PwdCache(object):
267 267 """Short term cache, used to preserve passwords
268 268 if they are used twice during a command"""
269 269 def __init__(self):
270 270 self._cache = {}
271 271
272 272 def store(self, realm, url, user, pwd):
273 273 """Saves password"""
274 274 cache_key = (realm, url, user)
275 275 self._cache[cache_key] = pwd
276 276
277 277 def check(self, realm, url, user):
278 278 """Checks for cached password"""
279 279 cache_key = (realm, url, user)
280 280 return self._cache.get(cache_key)
281 281
282 282
283 283 _re_http_url = re.compile(b'^https?://')
284 284
285 285
286 286 def is_http_path(url):
287 287 return bool(_re_http_url.search(url))
288 288
289 289
290 290 def make_passwordmgr(ui):
291 291 """Constructing passwordmgr in a way compatible with various mercurials"""
292 292 if hasattr(ui, 'httppasswordmgrdb'):
293 293 return passwordmgr(ui, ui.httppasswordmgrdb)
294 294 else:
295 295 return passwordmgr(ui)
296 296
297 297 ############################################################
298 298 # HTTP password management
299 299 ############################################################
300 300
301 301
302 302 class HTTPPasswordHandler(object):
303 303 """
304 304 Actual implementation of password handling (user prompting,
305 305 configuration file searching, keyring save&restore).
306 306
307 307 Object of this class is bound as passwordmgr attribute.
308 308 """
309 309 def __init__(self):
310 310 self.pwd_cache = PwdCache()
311 311 self.last_reply = None
312 312
313 313 # Markers and also names used in debug notes. Password source
314 314 SRC_URL = "repository URL"
315 315 SRC_CFGAUTH = "hgrc"
316 316 SRC_MEMCACHE = "temporary cache"
317 317 SRC_URLCACHE = "urllib temporary cache"
318 318 SRC_KEYRING = "keyring"
319 319
320 320 def get_credentials(self, pwmgr, realm, authuri, skip_caches=False):
321 321 """
322 322 Looks up for user credentials in various places, returns them
323 323 and information about their source.
324 324
325 325 Used internally inside find_auth and inside informative
326 326 commands (thiis method doesn't cache, doesn't detect bad
327 327 passwords etc, doesn't prompt interactively, doesn't store
328 328 password in keyring).
329 329
330 330 Returns: user, password, SRC_*, actual_url
331 331
332 332 If not found, password and SRC is None, user can be given or
333 333 not, url is always set
334 334 """
335 335 ui = pwmgr.ui
336 336
337 337 parsed_url, url_user, url_passwd = self.unpack_url(authuri)
338 338 base_url = bytes(parsed_url)
339 339 ui.debug(meu.ui_string('keyring: base url: %s, url user: %s, url pwd: %s\n',
340 340 base_url, url_user, url_passwd and b'******' or b''))
341 341
342 342 # Extract username (or password) stored directly in url
343 343 if url_user and url_passwd:
344 344 return url_user, url_passwd, self.SRC_URL, base_url
345 345
346 346 # Extract data from urllib (in case it was already stored)
347 347 if isinstance(pwmgr, HTTPPasswordMgrWithDefaultRealm):
348 348 urllib_user, urllib_pwd = \
349 349 HTTPPasswordMgrWithDefaultRealm.find_user_password(
350 350 pwmgr, realm, authuri)
351 351 else:
352 352 urllib_user, urllib_pwd = pwmgr.passwddb.find_user_password(
353 353 realm, authuri)
354 354 if urllib_user and urllib_pwd:
355 355 return urllib_user, urllib_pwd, self.SRC_URLCACHE, base_url
356 356
357 357 actual_user = url_user or urllib_user
358 358
359 359 # Consult configuration to normalize url to prefix, and find username
360 360 # (and maybe password)
361 361 auth_user, auth_pwd, keyring_url = self.get_url_config(
362 362 ui, parsed_url, actual_user)
363 363 if auth_user and actual_user and (actual_user != auth_user):
364 364 raise error.Abort(_('keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, actual_user, auth_user)))
365 365 if auth_user and auth_pwd:
366 366 return auth_user, auth_pwd, self.SRC_CFGAUTH, keyring_url
367 367
368 368 actual_user = actual_user or auth_user
369 369
370 370 if skip_caches:
371 371 return actual_user, None, None, keyring_url
372 372
373 373 # Check memory cache (reuse )
374 374 # Checking the memory cache (there may be many http calls per command)
375 375 cached_pwd = self.pwd_cache.check(realm, keyring_url, actual_user)
376 376 if cached_pwd:
377 377 return actual_user, cached_pwd, self.SRC_MEMCACHE, keyring_url
378 378
379 379 # Load from keyring.
380 380 if actual_user:
381 381 ui.debug(meu.ui_string("keyring: looking for password (user %s, url %s)\n",
382 382 actual_user, keyring_url))
383 383 keyring_pwd = password_store.get_http_password(keyring_url, actual_user)
384 384 if keyring_pwd:
385 385 return actual_user, keyring_pwd, self.SRC_KEYRING, keyring_url
386 386
387 387 return actual_user, None, None, keyring_url
388 388
389 389 @staticmethod
390 390 def prompt_interactively(ui, user, realm, url):
391 391 """Actual interactive prompt"""
392 392 if not ui.interactive():
393 393 raise error.Abort(_('keyring: http authorization required but program used in non-interactive mode'))
394 394
395 395 if not user:
396 396 ui.status(meu.ui_string("keyring: username not specified in hgrc (or in url). Password will not be saved.\n"))
397 397
398 398 ui.write(meu.ui_string("http authorization required\n"))
399 399 ui.status(meu.ui_string("realm: %s\n",
400 400 realm))
401 401 ui.status(meu.ui_string("url: %s\n",
402 402 url))
403 403 if user:
404 404 ui.write(meu.ui_string("user: %s (fixed in hgrc or url)\n",
405 405 user))
406 406 else:
407 407 user = ui.prompt(meu.ui_string("user:"),
408 408 default=None)
409 409 pwd = ui.getpass(meu.ui_string("password: "))
410 410 return user, pwd
411 411
412 412 def find_auth(self, pwmgr, realm, authuri, req):
413 413 """
414 414 Actual implementation of find_user_password - different
415 415 ways of obtaining the username and password.
416 416
417 417 Returns pair username, password
418 418 """
419 419 ui = pwmgr.ui
420 420 after_bad_auth = self._after_bad_auth(ui, realm, authuri, req)
421 421
422 422 # Look in url, cache, etc
423 423 user, pwd, src, final_url = self.get_credentials(
424 424 pwmgr, realm, authuri, skip_caches=after_bad_auth)
425 425 if pwd:
426 426 if src != self.SRC_MEMCACHE:
427 427 self.pwd_cache.store(realm, final_url, user, pwd)
428 428 self._note_last_reply(realm, authuri, user, req)
429 429 ui.debug(meu.ui_string("keyring: Password found in %s\n",
430 430 src))
431 431 return user, pwd
432 432
433 433 # Last resort: interactive prompt
434 434 user, pwd = self.prompt_interactively(ui, user, realm, final_url)
435 435
436 436 if user:
437 437 # Saving password to the keyring.
438 438 # It is done only if username is permanently set.
439 439 # Otherwise we won't be able to find the password so it
440 440 # does not make much sense to preserve it
441 441 ui.debug(meu.ui_string("keyring: Saving password for %s to keyring\n",
442 442 user))
443 443 try:
444 444 password_store.set_http_password(final_url, user, pwd)
445 445 except Exception as e:
446 446 keyring = import_keyring()
447 447 if isinstance(e, keyring.errors.PasswordSetError):
448 448 ui.traceback()
449 449 ui.warn(meu.ui_string("warning: failed to save password in keyring\n"))
450 450 else:
451 451 raise e
452 452
453 453 # Saving password to the memory cache
454 454 self.pwd_cache.store(realm, final_url, user, pwd)
455 455 self._note_last_reply(realm, authuri, user, req)
456 456 ui.debug(meu.ui_string("keyring: Manually entered password\n"))
457 457 return user, pwd
458 458
459 459 def get_url_config(self, ui, parsed_url, user):
460 460 """
461 461 Checks configuration to decide whether/which username, prefix,
462 462 and password are configured for given url. Consults [auth] section.
463 463
464 464 Returns tuple (username, password, prefix) containing elements
465 465 found. username and password can be None (if unset), if prefix
466 466 is not found, url itself is returned.
467 467 """
468 468 from mercurial.httpconnection import readauthforuri
469 469 ui.debug(meu.ui_string("keyring: checking for hgrc info about url %s, user %s\n",
470 470 parsed_url, user))
471 471
472 472 res = readauthforuri(ui, bytes(parsed_url), user)
473 473 # If it user-less version not work, let's try with added username to handle
474 474 # both config conventions
475 475 if (not res) and user:
476 476 parsed_url.user = user
477 477 res = readauthforuri(ui, bytes(parsed_url), user)
478 478 parsed_url.user = None
479 479 if res:
480 480 group, auth_token = res
481 481 else:
482 482 auth_token = None
483 483
484 484 if auth_token:
485 485 username = auth_token.get(b'username')
486 486 password = auth_token.get(b'password')
487 487 prefix = auth_token.get(b'prefix')
488 488 else:
489 489 username = user
490 490 password = None
491 491 prefix = None
492 492
493 493 password_url = self.password_url(bytes(parsed_url), prefix)
494 494
495 495 ui.debug(meu.ui_string("keyring: Password url: %s, user: %s, password: %s (prefix: %s)\n",
496 496 password_url, username,
497 497 b'********' if password else b'',
498 498 prefix))
499 499
500 500 return username, password, password_url
501 501
502 502 def _note_last_reply(self, realm, authuri, user, req):
503 503 """
504 504 Internal helper. Saves info about auth-data obtained,
505 505 preserves them in last_reply, and returns pair user, pwd
506 506 """
507 507 self.last_reply = dict(realm=realm, authuri=authuri,
508 508 user=user, req=req)
509 509
510 510 def _after_bad_auth(self, ui, realm, authuri, req):
511 511 """
512 512 If we are called again just after identical previous
513 513 request, then the previously returned auth must have been
514 514 wrong. So we note this to force password prompt (and avoid
515 515 reusing bad password indefinitely).
516 516
517 517 This routine checks for this condition.
518 518 """
519 519 if self.last_reply:
520 520 if (self.last_reply['realm'] == realm) \
521 521 and (self.last_reply['authuri'] == authuri) \
522 522 and (self.last_reply['req'] == req):
523 523 ui.debug(meu.ui_string(
524 524 "keyring: Working after bad authentication, cached passwords not used %s\n",
525 525 str(self.last_reply)))
526 526 return True
527 527 return False
528 528
529 529 @staticmethod
530 530 def password_url(base_url, prefix):
531 531 """Calculates actual url identifying the password. Takes
532 532 configured prefix under consideration (so can be shorter
533 533 than repo url)"""
534 534 if not prefix or prefix == b'*':
535 535 return base_url
536 536 scheme, hostpath = base_url.split(b'://', 1)
537 537 p = prefix.split(b'://', 1)
538 538 if len(p) > 1:
539 539 prefix_host_path = p[1]
540 540 else:
541 541 prefix_host_path = prefix
542 542 password_url = scheme + b'://' + prefix_host_path
543 543 return password_url
544 544
545 545 @staticmethod
546 546 def unpack_url(authuri):
547 547 """
548 548 Takes original url for which authentication is attempted and:
549 549
550 550 - Strips query params from url. Used to convert urls like
551 551 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
552 552 to
553 553 https://repo.machine.com/repos/apps/module
554 554
555 555 - Extracts username and password, if present, and removes them from url
556 556 (so prefix matching works properly)
557 557
558 558 Returns url, user, password
559 559 where url is mercurial.util.url object already stripped of all those
560 560 params.
561 561 """
562 562 # In case of py3, util.url expects bytes
563 563 authuri = meu.pycompat.bytestr(authuri)
564 564
565 565 # mercurial.util.url, rather handy url parser
566 566 parsed_url = util.url(authuri)
567 567 parsed_url.query = b''
568 568 parsed_url.fragment = None
569 569 # Strip arguments to get actual remote repository url.
570 570 # base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
571 571 # parsed_url.path)
572 572 user = parsed_url.user
573 573 passwd = parsed_url.passwd
574 574 parsed_url.user = None
575 575 parsed_url.passwd = None
576 576
577 577 return parsed_url, user, passwd
578 578
579 579
580 580 ############################################################
581 581 # Mercurial monkey-patching
582 582 ############################################################
583 583
584 584
585 585 @monkeypatch_method(passwordmgr)
586 586 def find_user_password(self, realm, authuri):
587 587 """
588 588 keyring-based implementation of username/password query
589 589 for HTTP(S) connections
590 590
591 591 Passwords are saved in gnome keyring, OSX/Chain or other platform
592 592 specific storage and keyed by the repository url
593 593 """
594 594 # In sync with hg 5.0
595 595 assert isinstance(realm, (type(None), str))
596 596 assert isinstance(authuri, str)
597 597
598 598 # Extend object attributes
599 599 if not hasattr(self, '_pwd_handler'):
600 600 self._pwd_handler = HTTPPasswordHandler()
601 601
602 602 if hasattr(self, '_http_req'):
603 603 req = self._http_req
604 604 else:
605 605 req = None
606 606
607 607 return self._pwd_handler.find_auth(self, realm, authuri, req)
608 608
609 609
610 610 @monkeypatch_method(AbstractBasicAuthHandler, "http_error_auth_reqed")
611 611 def basic_http_error_auth_reqed(self, authreq, host, req, headers):
612 612 """Preserves current HTTP request so it can be consulted
613 613 in find_user_password above"""
614 614 self.passwd._http_req = req
615 615 try:
616 616 return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
617 617 finally:
618 618 self.passwd._http_req = None
619 619
620 620
621 621 @monkeypatch_method(AbstractDigestAuthHandler, "http_error_auth_reqed")
622 622 def digest_http_error_auth_reqed(self, authreq, host, req, headers):
623 623 """Preserves current HTTP request so it can be consulted
624 624 in find_user_password above"""
625 625 self.passwd._http_req = req
626 626 try:
627 627 return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
628 628 finally:
629 629 self.passwd._http_req = None
630 630
631 631 ############################################################
632 632 # SMTP support
633 633 ############################################################
634 634
635 635
636 636 def try_smtp_login(ui, smtp_obj, username, password):
637 637 """
638 638 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
639 639 password.
640 640
641 641 Returns:
642 642 - True if login succeeded
643 643 - False if login failed due to the wrong credentials
644 644
645 645 Throws Abort exception if login failed for any other reason.
646 646
647 647 Immediately returns False if password is empty
648 648 """
649 649 if not password:
650 650 return False
651 651 try:
652 652 ui.note(_('(authenticating to mail server as %s)\n') %
653 653 username)
654 654 smtp_obj.login(username, password)
655 655 return True
656 656 except smtplib.SMTPException as inst:
657 657 if inst.smtp_code == 535:
658 658 ui.status(meu.ui_string("SMTP login failed: %s\n\n",
659 659 inst.smtp_error))
660 660 return False
661 661 else:
662 662 raise error.Abort(inst)
663 663
664 664
665 665 def keyring_supported_smtp(ui, username):
666 666 """
667 667 keyring-integrated replacement for mercurial.mail._smtp Used only
668 668 when configuration file contains username, but does not contain
669 669 the password.
670 670
671 671 Most of the routine below is copied as-is from
672 672 mercurial.mail._smtp. The critical changed part is marked with #
673 673 >>>>> and # <<<<< markers, there are also some fixes which make
674 674 the code working on various Mercurials (like parsebool import).
675 675 """
676 676 try:
677 677 from mercurial.utils.stringutil import parsebool
678 678 except ImportError:
679 679 from mercurial.utils import parsebool
680 680
681 681 local_hostname = ui.config('smtp', 'local_hostname')
682 682 tls = ui.config('smtp', 'tls', 'none')
683 683 # backward compatible: when tls = true, we use starttls.
684 684 starttls = tls == 'starttls' or parsebool(tls)
685 685 smtps = tls == 'smtps'
686 686 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
687 687 raise error.Abort(_("can't use TLS: Python SSL support not installed"))
688 688 mailhost = ui.config('smtp', 'host')
689 689 if not mailhost:
690 690 raise error.Abort(_('smtp.host not configured - cannot send mail'))
691 691 if getattr(sslutil, 'sslkwargs', None) is None:
692 692 sslkwargs = None
693 693 elif starttls or smtps:
694 694 sslkwargs = sslutil.sslkwargs(ui, mailhost)
695 695 else:
696 696 sslkwargs = {}
697 697 if smtps:
698 698 ui.note(_('(using smtps)\n'))
699 699
700 700 # mercurial 3.8 added a mandatory host arg
701 701 if not sslkwargs:
702 702 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
703 703 elif 'host' in SMTPS.__init__.__code__.co_varnames:
704 704 s = SMTPS(sslkwargs, local_hostname=local_hostname, host=mailhost)
705 705 else:
706 706 s = SMTPS(sslkwargs, local_hostname=local_hostname)
707 707 elif starttls:
708 708 if not sslkwargs:
709 709 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
710 710 elif 'host' in STARTTLS.__init__.__code__.co_varnames:
711 711 s = STARTTLS(sslkwargs, local_hostname=local_hostname, host=mailhost)
712 712 else:
713 713 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
714 714 else:
715 715 s = smtplib.SMTP(local_hostname=local_hostname)
716 716 if smtps:
717 717 defaultport = 465
718 718 else:
719 719 defaultport = 25
720 720 mailport = util.getport(ui.config('smtp', 'port', defaultport))
721 721 ui.note(_('sending mail: smtp host %s, port %s\n') %
722 722 (mailhost, mailport))
723 723 s.connect(host=mailhost, port=mailport)
724 724 if starttls:
725 725 ui.note(_('(using starttls)\n'))
726 726 s.ehlo()
727 727 s.starttls()
728 728 s.ehlo()
729 729 if starttls or smtps:
730 730 if getattr(sslutil, 'validatesocket', None):
731 731 ui.note(_('(verifying remote certificate)\n'))
732 732 sslutil.validatesocket(s.sock)
733 733
734 734 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
735 735 stored = password = password_store.get_smtp_password(
736 736 mailhost, mailport, username)
737 737 # No need to check whether password was found as try_smtp_login
738 738 # just returns False if it is absent.
739 739 while not try_smtp_login(ui, s, username, password):
740 740 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
741 741
742 742 if stored != password:
743 743 password_store.set_smtp_password(
744 744 mailhost, mailport, username, password)
745 745 # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
746 746
747 747 def send(sender, recipients, msg):
748 748 try:
749 749 return s.sendmail(sender, recipients, msg)
750 750 except smtplib.SMTPRecipientsRefused as inst:
751 751 recipients = [r[1] for r in inst.recipients.values()]
752 752 raise error.Abort('\n' + '\n'.join(recipients))
753 753 except smtplib.SMTPException as inst:
754 754 raise error.Abort(inst)
755 755
756 756 return send
757 757
758 758 ############################################################
759 759 # SMTP monkeypatching
760 760 ############################################################
761 761
762 762
763 763 @monkeypatch_method(mail)
764 764 def _smtp(ui):
765 765 """
766 766 build an smtp connection and return a function to send email
767 767
768 768 This is the monkeypatched version of _smtp(ui) function from
769 769 mercurial/mail.py. It calls the original unless username
770 770 without password is given in the configuration.
771 771 """
772 772 username = ui.config('smtp', 'username')
773 773 password = ui.config('smtp', 'password')
774 774
775 775 if username and not password:
776 776 return keyring_supported_smtp(ui, username)
777 777 else:
778 778 return _smtp.orig(ui)
779 779
780 780
781 781 ############################################################
782 782 # Custom commands
783 783 ############################################################
784 784
785 785 cmdtable = {}
786 786 command = meu.command(cmdtable)
787 787
788 788
789 789 @command(b'keyring_check',
790 790 [],
791 791 _("keyring_check [PATH]"),
792 792 optionalrepo=True)
793 793 def cmd_keyring_check(ui, repo, *path_args, **opts): # pylint: disable=unused-argument
794 794 """
795 795 Prints basic info (whether password is currently saved, and how is
796 796 it identified) for given path.
797 797
798 798 Can be run without parameters to show status for all (current repository) paths which
799 799 are HTTP-like.
800 800 """
801 801 defined_paths = [(name, url)
802 802 for name, url in ui.configitems(b'paths')]
803 803 if path_args:
804 804 # Maybe parameter is an alias
805 805 defined_paths_dic = dict(defined_paths)
806 806 paths = [(path_arg, defined_paths_dic.get(path_arg, path_arg))
807 807 for path_arg in path_args]
808 808 else:
809 809 if not repo:
810 810 ui.status(meu.ui_string("Url to check not specified. Either run ``hg keyring_check https://...``, or run the command inside some repository (to test all defined paths).\n"))
811 811 return
812 812 paths = [(name, url) for name, url in defined_paths]
813 813
814 814 if not paths:
815 815 ui.status(meu.ui_string("keyring_check: no paths defined\n"))
816 816 return
817 817
818 818 handler = HTTPPasswordHandler()
819 819
820 820 ui.status(meu.ui_string("keyring password save status:\n"))
821 821 for name, url in paths:
822 822 if not is_http_path(url):
823 823 if path_args:
824 824 ui.status(meu.ui_string(" %s: non-http path (%s)\n",
825 825 name, url))
826 826 continue
827 827 user, pwd, source, final_url = handler.get_credentials(
828 828 make_passwordmgr(ui), name, url)
829 829 if pwd:
830 830 ui.status(meu.ui_string(
831 831 " %s: password available, source: %s, bound to user %s, url %s\n",
832 832 name, source, user, final_url))
833 833 elif user:
834 834 ui.status(meu.ui_string(
835 835 " %s: password not available, once entered, will be bound to user %s, url %s\n",
836 836 name, user, final_url))
837 837 else:
838 838 ui.status(meu.ui_string(
839 839 " %s: password not available, user unknown, url %s\n",
840 840 name, final_url))
841 841
842 842
843 843 @command(b'keyring_clear',
844 844 [],
845 845 _('hg keyring_clear PATH-OR-ALIAS'),
846 846 optionalrepo=True)
847 847 def cmd_keyring_clear(ui, repo, path, **opts): # pylint: disable=unused-argument
848 848 """
849 849 Drops password bound to given path (if any is saved).
850 850
851 851 Parameter can be given as full url (``https://John@bitbucket.org``) or as the name
852 852 of path alias (``bitbucket``).
853 853 """
854 854 path_url = path
855 855 for name, url in ui.configitems(b'paths'):
856 856 if name == path:
857 857 path_url = url
858 858 break
859 859 if not is_http_path(path_url):
860 860 ui.status(meu.ui_string(
861 861 "%s is not a http path (and %s can't be resolved as path alias)\n",
862 862 path, path_url))
863 863 return
864 864
865 865 handler = HTTPPasswordHandler()
866 866
867 867 user, pwd, source, final_url = handler.get_credentials(
868 868 make_passwordmgr(ui), path, path_url)
869 869 if not user:
870 870 ui.status(meu.ui_string("Username not configured for url %s\n",
871 871 final_url))
872 872 return
873 873 if not pwd:
874 874 ui.status(meu.ui_string("No password is saved for user %s, url %s\n",
875 875 user, final_url))
876 876 return
877 877
878 878 if source != handler.SRC_KEYRING:
879 879 ui.status(meu.ui_string("Password for user %s, url %s is saved in %s, not in keyring\n",
880 880 user, final_url, source))
881 881
882 882 password_store.clear_http_password(final_url, user)
883 883 ui.status(meu.ui_string("Password removed for user %s, url %s\n",
884 884 user, final_url))
885 885
886 886
887 buglink = 'https://bitbucket.org/Mekk/mercurial_keyring/issues'
887 buglink = 'https://foss.heptapod.net/mercurial/mercurial_keyring/issues'
@@ -1,37 +1,37 b''
1 1 """Setup for mercurial_keyring."""
2 2
3 3 from setuptools import setup
4 4
5 5 VERSION = '1.3.0'
6 6 LONG_DESCRIPTION = open("README.rst").read()
7 7
8 8 setup(
9 9 name="mercurial_keyring",
10 10 version=VERSION,
11 11 author='Marcin Kasperski',
12 12 author_email='Marcin.Kasperski@mekk.waw.pl',
13 url='http://bitbucket.org/Mekk/mercurial_keyring',
13 url='https://foss.heptapod.net/mercurial/mercurial_keyring',
14 14 description='Mercurial Keyring Extension',
15 15 long_description=LONG_DESCRIPTION,
16 16 license='BSD',
17 17 py_modules=['mercurial_keyring'],
18 18 keywords="mercurial hg keyring password",
19 19 classifiers=[
20 20 'Development Status :: 4 - Beta',
21 21 'Environment :: Console',
22 22 'Intended Audience :: Developers',
23 23 'License :: DFSG approved',
24 24 'License :: OSI Approved :: BSD License',
25 25 'Programming Language :: Python :: 2',
26 26 'Programming Language :: Python :: 3',
27 27 'Operating System :: OS Independent',
28 28 'Topic :: Software Development :: Libraries',
29 29 'Topic :: Software Development :: Libraries :: Python Modules',
30 30 'Topic :: Software Development :: Version Control'
31 31 ],
32 32 install_requires=[
33 33 'keyring>=0.3',
34 34 'mercurial_extension_utils>=1.5.0',
35 35 ],
36 36 zip_safe=True,
37 37 )
General Comments 0
You need to be logged in to leave comments. Login now