##// END OF EJS Templates
Working version. Password is actually saved and restored,...
Marcin Kasperski -
r7:6f5398b6 default
parent child Browse files
Show More
@@ -0,0 +1,27 b''
1 #!/usr/bin/python
2 #
3 # mercurial - scalable distributed SCM
4 #
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 #
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
9
10 # enable importing on demand to reduce startup time
11 try:
12 from mercurial import demandimport; demandimport.enable()
13 except ImportError:
14 import sys
15 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
16 ' '.join(sys.path))
17 sys.stderr.write("(check your install and PYTHONPATH)\n")
18 sys.exit(-1)
19
20 import sys
21 import mercurial.util
22 import mercurial.dispatch
23
24 for fp in (sys.stdin, sys.stdout, sys.stderr):
25 mercurial.util.set_binary(fp)
26
27 mercurial.dispatch.run()
@@ -1,146 +1,151 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 """
3 """
4 Storing HTTP authentication passwords in keyring database.
4 Storing HTTP authentication passwords in keyring database.
5
5
6 Installation method(s):
6 Installation method(s):
7
7
8 1) in ~/.hgrc (or /etc/hgext/...)
8 1) in ~/.hgrc (or /etc/hgext/...)
9
9
10 [extensions]
10 [extensions]
11 ...
11 ...
12 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
12 hgext.mercurial_keyring = /path/to/mercurial_keyring.py
13
13
14
14
15 2) Drop this file to hgext directory and in ~/.hgrc
15 2) Drop this file to hgext directory and in ~/.hgrc
16
16
17 [extensions]
17 [extensions]
18 hgext.mercurial_keyring =
18 hgext.mercurial_keyring =
19
19
20 """
20 """
21
21
22 #import mercurial.demandimport
22 #import mercurial.demandimport
23 #mercurial.demandimport.disable()
23 #mercurial.demandimport.disable()
24
24
25 from mercurial import hg, repo, util
25 from mercurial import hg, repo, util
26 from mercurial.i18n import _
26 from mercurial.i18n import _
27 try:
27 try:
28 from mercurial.url import passwordmgr
28 from mercurial.url import passwordmgr
29 except:
29 except:
30 from mercurial.httprepo import passwordmgr
30 from mercurial.httprepo import passwordmgr
31
31
32 import keyring
32 import keyring
33 import getpass
33 import getpass
34 from urlparse import urlparse
34 from urlparse import urlparse
35 import urllib2
35 import urllib2
36
36
37 KEYRING_SERVICE = "Mercurial"
37 KEYRING_SERVICE = "Mercurial"
38
38
39 ############################################################
39 ############################################################
40
40
41 def monkeypatch_class(name, bases, namespace):
41 def monkeypatch_class(name, bases, namespace):
42 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
42 """http://mail.python.org/pipermail/python-dev/2008-January/076194.html"""
43 assert len(bases) == 1, "Exactly one base class required"
43 assert len(bases) == 1, "Exactly one base class required"
44 base = bases[0]
44 base = bases[0]
45 for name, value in namespace.iteritems():
45 for name, value in namespace.iteritems():
46 if name != "__metaclass__":
46 if name != "__metaclass__":
47 setattr(base, name, value)
47 setattr(base, name, value)
48 return base
48 return base
49
49
50 def monkeypatch_method(cls):
50 def monkeypatch_method(cls):
51 def decorator(func):
51 def decorator(func):
52 setattr(cls, func.__name__, func)
52 setattr(cls, func.__name__, func)
53 return func
53 return func
54 return decorator
54 return decorator
55
55
56 ############################################################
56 ############################################################
57
57
58 class PasswordStore(object):
58 class PasswordStore(object):
59 """
59 """
60 Helper object handling password save&restore. Passwords
60 Helper object handling password save&restore. Passwords
61 are saved both in local memory cache, and keyring, and are
61 are saved both in local memory cache, and keyring, and are
62 restored from those.
62 restored from those.
63 """
63 """
64 def __init__(self):
64 def __init__(self):
65 self.cache = dict()
65 self.cache = dict()
66 def save_password(self, url, username, password):
66 def get_password(self, url, username):
67 self.cache[url] = (username, password)
67 return keyring.get_password(KEYRING_SERVICE,
68 # TODO: keyring save
68 self._format_key(url, username))
69 def get_password(self, url):
69 def set_password(self, url, username, password):
70 r = self.cache.get(url)
70 keyring.set_password(KEYRING_SERVICE,
71 if r:
71 self._format_key(url, username),
72 return r
72 password)
73 # TODO: keyring restore
73 def _format_key(self, url, username):
74 return None, None
74 return "%s@@%s" % (username, url)
75
75
76 password_store = PasswordStore()
76 password_store = PasswordStore()
77
77
78 ############################################################
78 ############################################################
79
79
80 @monkeypatch_method(passwordmgr)
80 @monkeypatch_method(passwordmgr)
81 def find_user_password(self, realm, authuri):
81 def find_user_password(self, realm, authuri):
82 """
82 """
83 keyring-based implementation of username/password query
83 keyring-based implementation of username/password query
84
84
85 Passwords are saved in gnome keyring, OSX/Chain or other platform
85 Passwords are saved in gnome keyring, OSX/Chain or other platform
86 specific storage and keyed by the repository url
86 specific storage and keyed by the repository url
87 """
87 """
88 # Calculate the true remote url. authuri happens to contain things like
88 # Calculate the true remote url. authuri happens to contain things like
89 # https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
89 # https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
90 parsed_url = urlparse(authuri)
90 parsed_url = urlparse(authuri)
91 base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
91 base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc, parsed_url.path)
92
92
93 # Extracting possible username/password stored in directly in repository url
93 # Extracting possible username/password stored in directly in repository url
94 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
94 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authuri)
95
95
96 # Checking the local cache (single command may repeat the call many
96 # Checking the local cache (single command may repeat the call many
97 # times)
97 # times)
98 if not hasattr(self, '_pwd_cache'):
98 if not hasattr(self, '_pwd_cache'):
99 self._pwd_cache = {}
99 self._pwd_cache = {}
100 cache_key = (realm, base_url)
100 cache_key = (realm, base_url)
101 cached_auth = self._pwd_cache.get(cache_key)
101 cached_auth = self._pwd_cache.get(cache_key)
102 if cached_auth:
102 if cached_auth:
103 return cached_auth
103 return cached_auth
104
104
105 # Loading username (and maybe password) from [auth] in local .hg/hgrc
105 # Loading username (and maybe password) from [auth] in local .hg/hgrc
106 if not user:
106 if not user:
107 # Lines below unfortunately do not work, readauthtoken
107 # Lines below unfortunately do not work, readauthtoken
108 # always return None. Why? Because
108 # always return None. Why? Because
109 # self.ui here describes the *remote* repository, so
109 # self.ui here describes the *remote* repository, so
110 # does *not* contain any option from local .hg/hgrc.
110 # does *not* contain any option from local .hg/hgrc.
111 #
111 #
112 #auth_token = self.readauthtoken(base_url)
112 #auth_token = self.readauthtoken(base_url)
113 #if auth_token:
113 #if auth_token:
114 # user, pwd = auth.get('username'), auth.get('password')
114 # user, pwd = auth.get('username'), auth.get('password')
115 #
115 #
116 # so - workaround
116 # so - workaround
117 repo_root = self.ui.config("bundle", "mainreporoot")
117 repo_root = self.ui.config("bundle", "mainreporoot")
118 if repo_root:
118 if repo_root:
119 from mercurial.ui import ui as _ui
119 from mercurial.ui import ui as _ui
120 import os
120 import os
121 local_ui = _ui(self.ui)
121 local_ui = _ui(self.ui)
122 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
122 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
123 local_passwordmgr = passwordmgr(local_ui)
123 local_passwordmgr = passwordmgr(local_ui)
124 auth_token = local_passwordmgr.readauthtoken(base_url)
124 auth_token = local_passwordmgr.readauthtoken(base_url)
125 if auth_token:
125 if auth_token:
126 user, pwd = auth.get('username'), auth.get('password')
126 user, pwd = auth_token.get('username'), auth_token.get('password')
127
128
129
130
131 user, pwd = password_store.get_password(base_url)
132 if user and pwd:
133 return user, pwd
134
127
135 if not self.ui.interactive():
128 # username still not known? Asking
136 raise util.Abort(_('mercurial_keyring: http authorization required'))
129 if not user:
137 self.ui.write(_("http authorization required\n"))
130 if not self.ui.interactive():
138 self.ui.status(_("realm: %s, url: %s\n" % (realm, base_url)))
131 raise util.Abort(_('mercurial_keyring: http authorization required'))
139 user = self.ui.prompt(_("user:"), default = user)
132 self.ui.write(_("http authorization required\n"))
140 pwd = self.ui.getpass(_("password: "))
133 self.ui.status(_("realm: %s\n") % realm)
134 user = self.ui.prompt(_("user:"), default=None)
135
136 # username known and still no password? Time to check keyring
137 if user and not pwd:
138 pwd = password_store.get_password(base_url, user)
141
139
142 password_store.save_password(base_url, user, pwd)
140 # password still not known? Asking
141 if not pwd:
142 if not self.ui.interactive():
143 raise util.Abort(_('mercurial_keyring: http authorization required'))
144 pwd = self.ui.getpass(_("password: "))
145 password_store.set_password(base_url, user, pwd)
146
147 self._pwd_cache[cache_key] = (user, pwd)
143
148
144 return user, pwd
149 return user, pwd
145 #return None, None
150 #return None, None
146
151
General Comments 0
You need to be logged in to leave comments. Login now