mercurial_keyring.py
105 lines
| 2.9 KiB
| text/x-python
|
PythonLexer
Marcin Kasperski
|
r0 | # -*- coding: utf-8 -*- | ||
""" | ||||
Storing HTTP authentication passwords in keyring database. | ||||
Installation method(s): | ||||
1) in ~/.hgrc (or /etc/hgext/...) | ||||
[extensions] | ||||
... | ||||
hgext.mercurial_keyring = /path/to/mercurial_keyring.py | ||||
Marcin Kasperski
|
r1 | |||
2) Drop this file to hgext directory and in ~/.hgrc | ||||
[extensions] | ||||
hgext.mercurial_keyring = | ||||
Marcin Kasperski
|
r0 | """ | ||
from mercurial import hg, repo, util | ||||
from mercurial.i18n import _ | ||||
try: | ||||
from mercurial.url import passwordmgr | ||||
except: | ||||
from mercurial.httprepo import passwordmgr | ||||
import keyring | ||||
import getpass | ||||
from urlparse import urlparse | ||||
Marcin Kasperski
|
r1 | KEYRING_SERVICE = "Mercurial" | ||
Marcin Kasperski
|
r0 | |||
Marcin Kasperski
|
r2 | ############################################################ | ||
def monkeypatch_class(name, bases, namespace): | ||||
"""http://mail.python.org/pipermail/python-dev/2008-January/076194.html""" | ||||
assert len(bases) == 1, "Exactly one base class required" | ||||
base = bases[0] | ||||
for name, value in namespace.iteritems(): | ||||
if name != "__metaclass__": | ||||
setattr(base, name, value) | ||||
return base | ||||
Marcin Kasperski
|
r0 | def monkeypatch_method(cls): | ||
def decorator(func): | ||||
setattr(cls, func.__name__, func) | ||||
return func | ||||
return decorator | ||||
Marcin Kasperski
|
r2 | ############################################################ | ||
class PasswordStore(object): | ||||
""" | ||||
Helper object handling password save&restore. Passwords | ||||
are saved both in local memory cache, and keyring, and are | ||||
restored from those. | ||||
""" | ||||
def __init__(self): | ||||
self.cache = dict() | ||||
def save_password(self, url, username, password): | ||||
self.cache[url] = (username, password) | ||||
# TODO: keyring save | ||||
def get_password(self, url): | ||||
r = self.cache.get(url) | ||||
if r: | ||||
return r | ||||
# TODO: keyring restore | ||||
return None, None | ||||
password_store = PasswordStore() | ||||
############################################################ | ||||
Marcin Kasperski
|
r0 | @monkeypatch_method(passwordmgr) | ||
def find_user_password(self, realm, authuri): | ||||
""" | ||||
keyring-based implementation of username/password query | ||||
Passwords are saved in gnome keyring, OSX/Chain or other platform | ||||
specific storage and keyed by the repository url | ||||
""" | ||||
# Calculate the true url. authuri happens to contain things like | ||||
# https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between | ||||
parsed_url = urlparse(authuri) | ||||
base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path) | ||||
print "find_user_password", realm, base_url | ||||
Marcin Kasperski
|
r2 | user, pwd = password_store.get_password(base_url) | ||
if user and pwd: | ||||
return user, pwd | ||||
Marcin Kasperski
|
r1 | |||
if not self.ui.interactive(): | ||||
raise util.Abort(_('mercurial_keyring: http authorization required')) | ||||
self.ui.write(_("http authorization required\n")) | ||||
self.ui.status(_("realm: %s, url: %s\n" % (realm, base_url))) | ||||
Marcin Kasperski
|
r2 | user = self.ui.prompt(_("user:"), default = user) | ||
pwd = self.ui.getpass(_("password: ")) | ||||
Marcin Kasperski
|
r1 | |||
Marcin Kasperski
|
r2 | password_store.save_password(base_url, user, pwd) | ||
Marcin Kasperski
|
r1 | |||
Marcin Kasperski
|
r2 | return user, pwd | ||
Marcin Kasperski
|
r1 | #return None, None | ||
Marcin Kasperski
|
r2 | |||