|
@@
-1,793
+1,801
b''
|
|
1
|
# -*- coding: utf-8 -*-
|
|
1
|
# -*- coding: utf-8 -*-
|
|
2
|
#
|
|
2
|
#
|
|
3
|
# mercurial_keyring: save passwords in password database
|
|
3
|
# mercurial_keyring: save passwords in password database
|
|
4
|
#
|
|
4
|
#
|
|
5
|
# Copyright (c) 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
|
|
5
|
# Copyright (c) 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
|
|
6
|
# All rights reserved.
|
|
6
|
# All rights reserved.
|
|
7
|
#
|
|
7
|
#
|
|
8
|
# Redistribution and use in source and binary forms, with or without
|
|
8
|
# Redistribution and use in source and binary forms, with or without
|
|
9
|
# modification, are permitted provided that the following conditions
|
|
9
|
# modification, are permitted provided that the following conditions
|
|
10
|
# are met:
|
|
10
|
# are met:
|
|
11
|
# 1. Redistributions of source code must retain the above copyright
|
|
11
|
# 1. Redistributions of source code must retain the above copyright
|
|
12
|
# notice, this list of conditions and the following disclaimer.
|
|
12
|
# notice, this list of conditions and the following disclaimer.
|
|
13
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
13
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
14
|
# notice, this list of conditions and the following disclaimer in the
|
|
14
|
# notice, this list of conditions and the following disclaimer in the
|
|
15
|
# documentation and/or other materials provided with the distribution.
|
|
15
|
# documentation and/or other materials provided with the distribution.
|
|
16
|
# 3. The name of the author may not be used to endorse or promote products
|
|
16
|
# 3. The name of the author may not be used to endorse or promote products
|
|
17
|
# derived from this software without specific prior written permission.
|
|
17
|
# derived from this software without specific prior written permission.
|
|
18
|
#
|
|
18
|
#
|
|
19
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
19
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
20
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
20
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
21
|
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
21
|
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
22
|
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
22
|
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
23
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
23
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
24
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
24
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
25
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
25
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
26
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
28
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
#
|
|
29
|
#
|
|
30
|
# See README.txt for more details.
|
|
30
|
# See README.txt for more details.
|
|
31
|
|
|
31
|
|
|
32
|
'''securely save HTTP and SMTP passwords to encrypted storage
|
|
32
|
'''securely save HTTP and SMTP passwords to encrypted storage
|
|
33
|
|
|
33
|
|
|
34
|
mercurial_keyring securely saves HTTP and SMTP passwords in password
|
|
34
|
mercurial_keyring securely saves HTTP and SMTP passwords in password
|
|
35
|
databases (Gnome Keyring, KDE KWallet, OSXKeyChain, Win32 crypto
|
|
35
|
databases (Gnome Keyring, KDE KWallet, OSXKeyChain, Win32 crypto
|
|
36
|
services).
|
|
36
|
services).
|
|
37
|
|
|
37
|
|
|
38
|
The process is automatic. Whenever bare Mercurial just prompts for
|
|
38
|
The process is automatic. Whenever bare Mercurial just prompts for
|
|
39
|
the password, Mercurial with mercurial_keyring enabled checks whether
|
|
39
|
the password, Mercurial with mercurial_keyring enabled checks whether
|
|
40
|
saved password is available first. If so, it is used. If not, you
|
|
40
|
saved password is available first. If so, it is used. If not, you
|
|
41
|
will be prompted for the password, but entered password will be
|
|
41
|
will be prompted for the password, but entered password will be
|
|
42
|
saved for the future use.
|
|
42
|
saved for the future use.
|
|
43
|
|
|
43
|
|
|
44
|
In case saved password turns out to be invalid (HTTP or SMTP login
|
|
44
|
In case saved password turns out to be invalid (HTTP or SMTP login
|
|
45
|
fails) it is dropped, and you are asked for current password.
|
|
45
|
fails) it is dropped, and you are asked for current password.
|
|
46
|
|
|
46
|
|
|
47
|
Actual password storage is implemented by Python keyring library, this
|
|
47
|
Actual password storage is implemented by Python keyring library, this
|
|
48
|
extension glues those services to Mercurial. Consult keyring
|
|
48
|
extension glues those services to Mercurial. Consult keyring
|
|
49
|
documentation for information how to configure actual password
|
|
49
|
documentation for information how to configure actual password
|
|
50
|
backend (by default keyring guesses, usually correctly, for example
|
|
50
|
backend (by default keyring guesses, usually correctly, for example
|
|
51
|
you get KDE Wallet under KDE, and Gnome Keyring under Gnome or Unity).
|
|
51
|
you get KDE Wallet under KDE, and Gnome Keyring under Gnome or Unity).
|
|
52
|
'''
|
|
52
|
'''
|
|
53
|
|
|
53
|
|
|
54
|
import urllib2
|
|
54
|
import urllib2
|
|
55
|
import smtplib
|
|
55
|
import smtplib
|
|
56
|
import socket
|
|
56
|
import socket
|
|
57
|
import os
|
|
57
|
import os
|
|
58
|
import sys
|
|
58
|
import sys
|
|
59
|
import re
|
|
59
|
import re
|
|
60
|
|
|
60
|
|
|
61
|
from mercurial import util, sslutil
|
|
61
|
from mercurial import util, sslutil
|
|
62
|
from mercurial.i18n import _
|
|
62
|
from mercurial.i18n import _
|
|
63
|
from mercurial.url import passwordmgr
|
|
63
|
from mercurial.url import passwordmgr
|
|
64
|
from mercurial import mail
|
|
64
|
from mercurial import mail
|
|
65
|
from mercurial.mail import SMTPS, STARTTLS
|
|
65
|
from mercurial.mail import SMTPS, STARTTLS
|
|
66
|
from mercurial import encoding
|
|
66
|
from mercurial import encoding
|
|
67
|
|
|
67
|
|
|
68
|
# pylint: disable=invalid-name, line-too-long, protected-access, too-many-arguments
|
|
68
|
# pylint: disable=invalid-name, line-too-long, protected-access, too-many-arguments
|
|
69
|
|
|
69
|
|
|
70
|
###########################################################################
|
|
70
|
###########################################################################
|
|
71
|
# Specific import trickery
|
|
71
|
# Specific import trickery
|
|
72
|
###########################################################################
|
|
72
|
###########################################################################
|
|
73
|
|
|
73
|
|
|
74
|
|
|
74
|
|
|
75
|
def import_meu():
|
|
75
|
def import_meu():
|
|
76
|
"""
|
|
76
|
"""
|
|
77
|
Convoluted import of mercurial_extension_utils, which helps
|
|
77
|
Convoluted import of mercurial_extension_utils, which helps
|
|
78
|
TortoiseHg/Win setups. This routine and it's use below
|
|
78
|
TortoiseHg/Win setups. This routine and it's use below
|
|
79
|
performs equivalent of
|
|
79
|
performs equivalent of
|
|
80
|
from mercurial_extension_utils import monkeypatch_method
|
|
80
|
from mercurial_extension_utils import monkeypatch_method
|
|
81
|
but looks for some non-path directories.
|
|
81
|
but looks for some non-path directories.
|
|
82
|
"""
|
|
82
|
"""
|
|
83
|
try:
|
|
83
|
try:
|
|
84
|
import mercurial_extension_utils
|
|
84
|
import mercurial_extension_utils
|
|
85
|
except ImportError:
|
|
85
|
except ImportError:
|
|
86
|
my_dir = os.path.dirname(__file__)
|
|
86
|
my_dir = os.path.dirname(__file__)
|
|
87
|
sys.path.extend([
|
|
87
|
sys.path.extend([
|
|
88
|
# In the same dir (manual or site-packages after pip)
|
|
88
|
# In the same dir (manual or site-packages after pip)
|
|
89
|
my_dir,
|
|
89
|
my_dir,
|
|
90
|
# Developer clone
|
|
90
|
# Developer clone
|
|
91
|
os.path.join(os.path.dirname(my_dir), "extension_utils"),
|
|
91
|
os.path.join(os.path.dirname(my_dir), "extension_utils"),
|
|
92
|
# Side clone
|
|
92
|
# Side clone
|
|
93
|
os.path.join(os.path.dirname(my_dir), "mercurial-extension_utils"),
|
|
93
|
os.path.join(os.path.dirname(my_dir), "mercurial-extension_utils"),
|
|
94
|
])
|
|
94
|
])
|
|
95
|
try:
|
|
95
|
try:
|
|
96
|
import mercurial_extension_utils
|
|
96
|
import mercurial_extension_utils
|
|
97
|
except ImportError:
|
|
97
|
except ImportError:
|
|
98
|
raise util.Abort(_("""Can not import mercurial_extension_utils.
|
|
98
|
raise util.Abort(_("""Can not import mercurial_extension_utils.
|
|
99
|
Please install this module in Python path.
|
|
99
|
Please install this module in Python path.
|
|
100
|
See Installation chapter in https://bitbucket.org/Mekk/mercurial_keyring/ for details
|
|
100
|
See Installation chapter in https://bitbucket.org/Mekk/mercurial_keyring/ for details
|
|
101
|
(and for info about TortoiseHG on Windows, or other bundled Python)."""))
|
|
101
|
(and for info about TortoiseHG on Windows, or other bundled Python)."""))
|
|
102
|
return mercurial_extension_utils
|
|
102
|
return mercurial_extension_utils
|
|
103
|
|
|
103
|
|
|
104
|
meu = import_meu()
|
|
104
|
meu = import_meu()
|
|
105
|
monkeypatch_method = meu.monkeypatch_method
|
|
105
|
monkeypatch_method = meu.monkeypatch_method
|
|
106
|
|
|
106
|
|
|
107
|
|
|
107
|
|
|
108
|
def import_keyring():
|
|
108
|
def import_keyring():
|
|
109
|
"""
|
|
109
|
"""
|
|
110
|
Importing keyring happens to be costly if wallet is slow, so we delay it
|
|
110
|
Importing keyring happens to be costly if wallet is slow, so we delay it
|
|
111
|
until it is really needed. The routine below also works around various
|
|
111
|
until it is really needed. The routine below also works around various
|
|
112
|
demandimport-related problems.
|
|
112
|
demandimport-related problems.
|
|
113
|
"""
|
|
113
|
"""
|
|
114
|
# mercurial.demandimport incompatibility workaround.
|
|
114
|
# mercurial.demandimport incompatibility workaround.
|
|
115
|
# various keyring backends fail as they can't properly import helper
|
|
115
|
# various keyring backends fail as they can't properly import helper
|
|
116
|
# modules (as demandimport modifies python import behaviour).
|
|
116
|
# modules (as demandimport modifies python import behaviour).
|
|
117
|
# If you get import errors with demandimport in backtrace, try
|
|
117
|
# If you get import errors with demandimport in backtrace, try
|
|
118
|
# guessing what to block and extending the list below.
|
|
118
|
# guessing what to block and extending the list below.
|
|
119
|
mod, was_imported_now = meu.direct_import_ext(
|
|
119
|
mod, was_imported_now = meu.direct_import_ext(
|
|
120
|
"keyring", [
|
|
120
|
"keyring", [
|
|
121
|
"gobject._gobject",
|
|
121
|
"gobject._gobject",
|
|
122
|
"configparser",
|
|
122
|
"configparser",
|
|
123
|
"json",
|
|
123
|
"json",
|
|
124
|
"abc",
|
|
124
|
"abc",
|
|
125
|
"io",
|
|
125
|
"io",
|
|
126
|
"keyring",
|
|
126
|
"keyring",
|
|
127
|
"gdata.docs.service",
|
|
127
|
"gdata.docs.service",
|
|
128
|
"gdata.service",
|
|
128
|
"gdata.service",
|
|
129
|
"types",
|
|
129
|
"types",
|
|
130
|
"atom.http",
|
|
130
|
"atom.http",
|
|
131
|
"atom.http_interface",
|
|
131
|
"atom.http_interface",
|
|
132
|
"atom.service",
|
|
132
|
"atom.service",
|
|
133
|
"atom.token_store",
|
|
133
|
"atom.token_store",
|
|
134
|
"ctypes",
|
|
134
|
"ctypes",
|
|
135
|
"secretstorage.exceptions",
|
|
135
|
"secretstorage.exceptions",
|
|
136
|
"fs.opener",
|
|
136
|
"fs.opener",
|
|
137
|
])
|
|
137
|
])
|
|
138
|
if was_imported_now:
|
|
138
|
if was_imported_now:
|
|
139
|
# Shut up warning about uninitialized logging by keyring
|
|
139
|
# Shut up warning about uninitialized logging by keyring
|
|
140
|
meu.disable_logging("keyring")
|
|
140
|
meu.disable_logging("keyring")
|
|
141
|
return mod
|
|
141
|
return mod
|
|
142
|
|
|
142
|
|
|
143
|
#################################################################
|
|
143
|
#################################################################
|
|
144
|
# Actual implementation
|
|
144
|
# Actual implementation
|
|
145
|
#################################################################
|
|
145
|
#################################################################
|
|
146
|
|
|
146
|
|
|
147
|
KEYRING_SERVICE = "Mercurial"
|
|
147
|
KEYRING_SERVICE = "Mercurial"
|
|
148
|
|
|
148
|
|
|
149
|
|
|
149
|
|
|
150
|
class PasswordStore(object):
|
|
150
|
class PasswordStore(object):
|
|
151
|
"""
|
|
151
|
"""
|
|
152
|
Helper object handling keyring usage (password save&restore,
|
|
152
|
Helper object handling keyring usage (password save&restore,
|
|
153
|
the way passwords are keyed in the keyring).
|
|
153
|
the way passwords are keyed in the keyring).
|
|
154
|
"""
|
|
154
|
"""
|
|
155
|
def __init__(self):
|
|
155
|
def __init__(self):
|
|
156
|
self.cache = dict()
|
|
156
|
self.cache = dict()
|
|
157
|
|
|
157
|
|
|
158
|
def get_http_password(self, url, username):
|
|
158
|
def get_http_password(self, url, username):
|
|
159
|
"""
|
|
159
|
"""
|
|
160
|
Checks whether password of username for url is available,
|
|
160
|
Checks whether password of username for url is available,
|
|
161
|
returns it or None
|
|
161
|
returns it or None
|
|
162
|
"""
|
|
162
|
"""
|
|
163
|
return self._read_password_from_keyring(
|
|
163
|
return self._read_password_from_keyring(
|
|
164
|
self._format_http_key(url, username))
|
|
164
|
self._format_http_key(url, username))
|
|
165
|
|
|
165
|
|
|
166
|
def set_http_password(self, url, username, password):
|
|
166
|
def set_http_password(self, url, username, password):
|
|
167
|
"""Saves password to keyring"""
|
|
167
|
"""Saves password to keyring"""
|
|
168
|
self._save_password_to_keyring(
|
|
168
|
self._save_password_to_keyring(
|
|
169
|
self._format_http_key(url, username),
|
|
169
|
self._format_http_key(url, username),
|
|
170
|
password)
|
|
170
|
password)
|
|
171
|
|
|
171
|
|
|
172
|
def clear_http_password(self, url, username):
|
|
172
|
def clear_http_password(self, url, username):
|
|
173
|
"""Drops saved password"""
|
|
173
|
"""Drops saved password"""
|
|
174
|
self.set_http_password(url, username, "")
|
|
174
|
self.set_http_password(url, username, "")
|
|
175
|
|
|
175
|
|
|
176
|
@staticmethod
|
|
176
|
@staticmethod
|
|
177
|
def _format_http_key(url, username):
|
|
177
|
def _format_http_key(url, username):
|
|
178
|
"""Construct actual key for password identification"""
|
|
178
|
"""Construct actual key for password identification"""
|
|
179
|
return "%s@@%s" % (username, url)
|
|
179
|
return "%s@@%s" % (username, url)
|
|
180
|
|
|
180
|
|
|
181
|
def get_smtp_password(self, machine, port, username):
|
|
181
|
def get_smtp_password(self, machine, port, username):
|
|
182
|
"""Checks for SMTP password in keyring, returns
|
|
182
|
"""Checks for SMTP password in keyring, returns
|
|
183
|
password or None"""
|
|
183
|
password or None"""
|
|
184
|
return self._read_password_from_keyring(
|
|
184
|
return self._read_password_from_keyring(
|
|
185
|
self._format_smtp_key(machine, port, username))
|
|
185
|
self._format_smtp_key(machine, port, username))
|
|
186
|
|
|
186
|
|
|
187
|
def set_smtp_password(self, machine, port, username, password):
|
|
187
|
def set_smtp_password(self, machine, port, username, password):
|
|
188
|
"""Saves SMTP password to keyring"""
|
|
188
|
"""Saves SMTP password to keyring"""
|
|
189
|
self._save_password_to_keyring(
|
|
189
|
self._save_password_to_keyring(
|
|
190
|
self._format_smtp_key(machine, port, username),
|
|
190
|
self._format_smtp_key(machine, port, username),
|
|
191
|
password)
|
|
191
|
password)
|
|
192
|
|
|
192
|
|
|
193
|
def clear_smtp_password(self, machine, port, username):
|
|
193
|
def clear_smtp_password(self, machine, port, username):
|
|
194
|
"""Drops saved SMTP password"""
|
|
194
|
"""Drops saved SMTP password"""
|
|
195
|
self.set_smtp_password(machine, port, username, "")
|
|
195
|
self.set_smtp_password(machine, port, username, "")
|
|
196
|
|
|
196
|
|
|
197
|
@staticmethod
|
|
197
|
@staticmethod
|
|
198
|
def _format_smtp_key(machine, port, username):
|
|
198
|
def _format_smtp_key(machine, port, username):
|
|
199
|
"""Construct key for SMTP password identification"""
|
|
199
|
"""Construct key for SMTP password identification"""
|
|
200
|
return "%s@@%s:%s" % (username, machine, str(port))
|
|
200
|
return "%s@@%s:%s" % (username, machine, str(port))
|
|
201
|
|
|
201
|
|
|
202
|
@staticmethod
|
|
202
|
@staticmethod
|
|
203
|
def _read_password_from_keyring(pwdkey):
|
|
203
|
def _read_password_from_keyring(pwdkey):
|
|
204
|
"""Physically read from keyring"""
|
|
204
|
"""Physically read from keyring"""
|
|
205
|
keyring = import_keyring()
|
|
205
|
keyring = import_keyring()
|
|
206
|
password = keyring.get_password(KEYRING_SERVICE, pwdkey)
|
|
206
|
password = keyring.get_password(KEYRING_SERVICE, pwdkey)
|
|
207
|
# Reverse recoding from next routine
|
|
207
|
# Reverse recoding from next routine
|
|
208
|
if isinstance(password, unicode):
|
|
208
|
if isinstance(password, unicode):
|
|
209
|
return encoding.tolocal(password.encode('utf-8'))
|
|
209
|
return encoding.tolocal(password.encode('utf-8'))
|
|
210
|
return password
|
|
210
|
return password
|
|
211
|
|
|
211
|
|
|
212
|
@staticmethod
|
|
212
|
@staticmethod
|
|
213
|
def _save_password_to_keyring(pwdkey, password):
|
|
213
|
def _save_password_to_keyring(pwdkey, password):
|
|
214
|
"""Physically write to keyring"""
|
|
214
|
"""Physically write to keyring"""
|
|
215
|
keyring = import_keyring()
|
|
215
|
keyring = import_keyring()
|
|
216
|
# keyring in general expects unicode.
|
|
216
|
# keyring in general expects unicode.
|
|
217
|
# Mercurial provides "local" encoding. See #33
|
|
217
|
# Mercurial provides "local" encoding. See #33
|
|
218
|
password = encoding.fromlocal(password).decode('utf-8')
|
|
218
|
password = encoding.fromlocal(password).decode('utf-8')
|
|
219
|
keyring.set_password(
|
|
219
|
keyring.set_password(
|
|
220
|
KEYRING_SERVICE, pwdkey, password)
|
|
220
|
KEYRING_SERVICE, pwdkey, password)
|
|
221
|
|
|
221
|
|
|
222
|
password_store = PasswordStore()
|
|
222
|
password_store = PasswordStore()
|
|
223
|
|
|
223
|
|
|
224
|
|
|
224
|
|
|
225
|
############################################################
|
|
225
|
############################################################
|
|
226
|
# Various utils
|
|
226
|
# Various utils
|
|
227
|
############################################################
|
|
227
|
############################################################
|
|
228
|
|
|
228
|
|
|
229
|
def _debug(ui, msg):
|
|
229
|
def _debug(ui, msg):
|
|
230
|
"""Generic debug message"""
|
|
230
|
"""Generic debug message"""
|
|
231
|
ui.debug("keyring: " + msg + "\n")
|
|
231
|
ui.debug("keyring: " + msg + "\n")
|
|
232
|
|
|
232
|
|
|
233
|
|
|
233
|
|
|
234
|
class PwdCache(object):
|
|
234
|
class PwdCache(object):
|
|
235
|
"""Short term cache, used to preserve passwords
|
|
235
|
"""Short term cache, used to preserve passwords
|
|
236
|
if they are used twice during a command"""
|
|
236
|
if they are used twice during a command"""
|
|
237
|
def __init__(self):
|
|
237
|
def __init__(self):
|
|
238
|
self._cache = {}
|
|
238
|
self._cache = {}
|
|
239
|
|
|
239
|
|
|
240
|
def store(self, realm, url, user, pwd):
|
|
240
|
def store(self, realm, url, user, pwd):
|
|
241
|
"""Saves password"""
|
|
241
|
"""Saves password"""
|
|
242
|
cache_key = (realm, url, user)
|
|
242
|
cache_key = (realm, url, user)
|
|
243
|
self._cache[cache_key] = pwd
|
|
243
|
self._cache[cache_key] = pwd
|
|
244
|
|
|
244
|
|
|
245
|
def check(self, realm, url, user):
|
|
245
|
def check(self, realm, url, user):
|
|
246
|
"""Checks for cached password"""
|
|
246
|
"""Checks for cached password"""
|
|
247
|
cache_key = (realm, url, user)
|
|
247
|
cache_key = (realm, url, user)
|
|
248
|
return self._cache.get(cache_key)
|
|
248
|
return self._cache.get(cache_key)
|
|
249
|
|
|
249
|
|
|
250
|
|
|
250
|
|
|
251
|
_re_http_url = re.compile(r'^https?://')
|
|
251
|
_re_http_url = re.compile(r'^https?://')
|
|
252
|
|
|
252
|
|
|
253
|
def is_http_path(url):
|
|
253
|
def is_http_path(url):
|
|
254
|
return bool(_re_http_url.search(url))
|
|
254
|
return bool(_re_http_url.search(url))
|
|
255
|
|
|
255
|
|
|
256
|
############################################################
|
|
256
|
############################################################
|
|
257
|
# HTTP password management
|
|
257
|
# HTTP password management
|
|
258
|
############################################################
|
|
258
|
############################################################
|
|
259
|
|
|
259
|
|
|
260
|
|
|
260
|
|
|
261
|
class HTTPPasswordHandler(object):
|
|
261
|
class HTTPPasswordHandler(object):
|
|
262
|
"""
|
|
262
|
"""
|
|
263
|
Actual implementation of password handling (user prompting,
|
|
263
|
Actual implementation of password handling (user prompting,
|
|
264
|
configuration file searching, keyring save&restore).
|
|
264
|
configuration file searching, keyring save&restore).
|
|
265
|
|
|
265
|
|
|
266
|
Object of this class is bound as passwordmgr attribute.
|
|
266
|
Object of this class is bound as passwordmgr attribute.
|
|
267
|
"""
|
|
267
|
"""
|
|
268
|
def __init__(self):
|
|
268
|
def __init__(self):
|
|
269
|
self.pwd_cache = PwdCache()
|
|
269
|
self.pwd_cache = PwdCache()
|
|
270
|
self.last_reply = None
|
|
270
|
self.last_reply = None
|
|
271
|
|
|
271
|
|
|
272
|
# Markers and also names used in debug notes. Password source
|
|
272
|
# Markers and also names used in debug notes. Password source
|
|
273
|
SRC_URL = "repository URL"
|
|
273
|
SRC_URL = "repository URL"
|
|
274
|
SRC_CFGAUTH = "hgrc"
|
|
274
|
SRC_CFGAUTH = "hgrc"
|
|
275
|
SRC_MEMCACHE = "temporary cache"
|
|
275
|
SRC_MEMCACHE = "temporary cache"
|
|
276
|
SRC_URLCACHE = "urllib temporary cache"
|
|
276
|
SRC_URLCACHE = "urllib temporary cache"
|
|
277
|
SRC_KEYRING = "keyring"
|
|
277
|
SRC_KEYRING = "keyring"
|
|
278
|
|
|
278
|
|
|
279
|
def get_credentials(self, pwmgr, realm, authuri, skip_caches=False):
|
|
279
|
def get_credentials(self, pwmgr, realm, authuri, skip_caches=False):
|
|
280
|
"""
|
|
280
|
"""
|
|
281
|
Looks up for user credentials in various places, returns them
|
|
281
|
Looks up for user credentials in various places, returns them
|
|
282
|
and information about their source.
|
|
282
|
and information about their source.
|
|
283
|
|
|
283
|
|
|
284
|
Used internally inside find_auth and inside informative
|
|
284
|
Used internally inside find_auth and inside informative
|
|
285
|
commands (thiis method doesn't cache, doesn't detect bad
|
|
285
|
commands (thiis method doesn't cache, doesn't detect bad
|
|
286
|
passwords etc, doesn't prompt interactively, doesn't store
|
|
286
|
passwords etc, doesn't prompt interactively, doesn't store
|
|
287
|
password in keyring).
|
|
287
|
password in keyring).
|
|
288
|
|
|
288
|
|
|
289
|
Returns: user, password, SRC_*, actual_url
|
|
289
|
Returns: user, password, SRC_*, actual_url
|
|
290
|
|
|
290
|
|
|
291
|
If not found, password and SRC is None, user can be given or
|
|
291
|
If not found, password and SRC is None, user can be given or
|
|
292
|
not, url is always set
|
|
292
|
not, url is always set
|
|
293
|
"""
|
|
293
|
"""
|
|
294
|
ui = pwmgr.ui
|
|
294
|
ui = pwmgr.ui
|
|
295
|
|
|
295
|
|
|
296
|
parsed_url, url_user, url_passwd = self.unpack_url(authuri)
|
|
296
|
parsed_url, url_user, url_passwd = self.unpack_url(authuri)
|
|
297
|
base_url = str(parsed_url)
|
|
297
|
base_url = str(parsed_url)
|
|
298
|
ui.debug(_('keyring: base url: %s, url user: %s, url pwd: %s\n') %
|
|
298
|
ui.debug(_('keyring: base url: %s, url user: %s, url pwd: %s\n') %
|
|
299
|
(base_url, url_user or '', url_passwd and '******' or ''))
|
|
299
|
(base_url, url_user or '', url_passwd and '******' or ''))
|
|
300
|
|
|
300
|
|
|
301
|
# Extract username (or password) stored directly in url
|
|
301
|
# Extract username (or password) stored directly in url
|
|
302
|
if url_user and url_passwd:
|
|
302
|
if url_user and url_passwd:
|
|
303
|
return url_user, url_passwd, self.SRC_URL, base_url
|
|
303
|
return url_user, url_passwd, self.SRC_URL, base_url
|
|
304
|
|
|
304
|
|
|
305
|
# Extract data from urllib (in case it was already stored)
|
|
305
|
# Extract data from urllib (in case it was already stored)
|
|
306
|
urllib_user, urllib_pwd \
|
|
306
|
urllib_user, urllib_pwd \
|
|
307
|
= urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
|
|
307
|
= urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
|
|
308
|
pwmgr, realm, authuri)
|
|
308
|
pwmgr, realm, authuri)
|
|
309
|
if urllib_user and urllib_pwd:
|
|
309
|
if urllib_user and urllib_pwd:
|
|
310
|
return urllib_user, urllib_pwd, self.SRC_URLCACHE, base_url
|
|
310
|
return urllib_user, urllib_pwd, self.SRC_URLCACHE, base_url
|
|
311
|
|
|
311
|
|
|
312
|
actual_user = url_user or urllib_user
|
|
312
|
actual_user = url_user or urllib_user
|
|
313
|
|
|
313
|
|
|
314
|
# Consult configuration to normalize url to prefix, and find username
|
|
314
|
# Consult configuration to normalize url to prefix, and find username
|
|
315
|
# (and maybe password)
|
|
315
|
# (and maybe password)
|
|
316
|
auth_user, auth_pwd, keyring_url = self.get_url_config(
|
|
316
|
auth_user, auth_pwd, keyring_url = self.get_url_config(
|
|
317
|
ui, parsed_url, actual_user)
|
|
317
|
ui, parsed_url, actual_user)
|
|
318
|
if auth_user and actual_user and (actual_user != auth_user):
|
|
318
|
if auth_user and actual_user and (actual_user != auth_user):
|
|
319
|
raise util.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)))
|
|
319
|
raise util.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)))
|
|
320
|
if auth_user and auth_pwd:
|
|
320
|
if auth_user and auth_pwd:
|
|
321
|
return auth_user, auth_pwd, self.SRC_CFGAUTH, keyring_url
|
|
321
|
return auth_user, auth_pwd, self.SRC_CFGAUTH, keyring_url
|
|
322
|
|
|
322
|
|
|
323
|
actual_user = actual_user or auth_user
|
|
323
|
actual_user = actual_user or auth_user
|
|
324
|
|
|
324
|
|
|
325
|
if skip_caches:
|
|
325
|
if skip_caches:
|
|
326
|
return actual_user, None, None, keyring_url
|
|
326
|
return actual_user, None, None, keyring_url
|
|
327
|
|
|
327
|
|
|
328
|
# Check memory cache (reuse )
|
|
328
|
# Check memory cache (reuse )
|
|
329
|
# Checking the memory cache (there may be many http calls per command)
|
|
329
|
# Checking the memory cache (there may be many http calls per command)
|
|
330
|
cached_pwd = self.pwd_cache.check(realm, keyring_url, actual_user)
|
|
330
|
cached_pwd = self.pwd_cache.check(realm, keyring_url, actual_user)
|
|
331
|
if cached_pwd:
|
|
331
|
if cached_pwd:
|
|
332
|
return actual_user, cached_pwd, self.SRC_MEMCACHE, keyring_url
|
|
332
|
return actual_user, cached_pwd, self.SRC_MEMCACHE, keyring_url
|
|
333
|
|
|
333
|
|
|
334
|
# Load from keyring.
|
|
334
|
# Load from keyring.
|
|
335
|
if actual_user:
|
|
335
|
if actual_user:
|
|
336
|
ui.debug(_("keyring: looking for password (user %s, url %s)\n") % (actual_user, keyring_url))
|
|
336
|
ui.debug(_("keyring: looking for password (user %s, url %s)\n") % (actual_user, keyring_url))
|
|
337
|
keyring_pwd = password_store.get_http_password(keyring_url, actual_user)
|
|
337
|
keyring_pwd = password_store.get_http_password(keyring_url, actual_user)
|
|
338
|
if keyring_pwd:
|
|
338
|
if keyring_pwd:
|
|
339
|
return actual_user, keyring_pwd, self.SRC_KEYRING, keyring_url
|
|
339
|
return actual_user, keyring_pwd, self.SRC_KEYRING, keyring_url
|
|
340
|
|
|
340
|
|
|
341
|
return actual_user, None, None, keyring_url
|
|
341
|
return actual_user, None, None, keyring_url
|
|
342
|
|
|
342
|
|
|
343
|
@staticmethod
|
|
343
|
@staticmethod
|
|
344
|
def prompt_interactively(ui, user, realm, url):
|
|
344
|
def prompt_interactively(ui, user, realm, url):
|
|
345
|
"""Actual interactive prompt"""
|
|
345
|
"""Actual interactive prompt"""
|
|
346
|
if not ui.interactive():
|
|
346
|
if not ui.interactive():
|
|
347
|
raise util.Abort(_('keyring: http authorization required but program used in non-interactive mode'))
|
|
347
|
raise util.Abort(_('keyring: http authorization required but program used in non-interactive mode'))
|
|
348
|
|
|
348
|
|
|
349
|
if not user:
|
|
349
|
if not user:
|
|
350
|
ui.status(_("keyring: username not specified in hgrc (or in url). Password will not be saved.\n"))
|
|
350
|
ui.status(_("keyring: username not specified in hgrc (or in url). Password will not be saved.\n"))
|
|
351
|
|
|
351
|
|
|
352
|
ui.write(_("http authorization required\n"))
|
|
352
|
ui.write(_("http authorization required\n"))
|
|
353
|
ui.status(_("realm: %s\n") % realm)
|
|
353
|
ui.status(_("realm: %s\n") % realm)
|
|
354
|
ui.status(_("url: %s\n") % url)
|
|
354
|
ui.status(_("url: %s\n") % url)
|
|
355
|
if user:
|
|
355
|
if user:
|
|
356
|
ui.write(_("user: %s (fixed in hgrc or url)\n" % user))
|
|
356
|
ui.write(_("user: %s (fixed in hgrc or url)\n" % user))
|
|
357
|
else:
|
|
357
|
else:
|
|
358
|
user = ui.prompt(_("user:"), default=None)
|
|
358
|
user = ui.prompt(_("user:"), default=None)
|
|
359
|
pwd = ui.getpass(_("password: "))
|
|
359
|
pwd = ui.getpass(_("password: "))
|
|
360
|
return user, pwd
|
|
360
|
return user, pwd
|
|
361
|
|
|
361
|
|
|
362
|
def find_auth(self, pwmgr, realm, authuri, req):
|
|
362
|
def find_auth(self, pwmgr, realm, authuri, req):
|
|
363
|
"""
|
|
363
|
"""
|
|
364
|
Actual implementation of find_user_password - different
|
|
364
|
Actual implementation of find_user_password - different
|
|
365
|
ways of obtaining the username and password.
|
|
365
|
ways of obtaining the username and password.
|
|
366
|
|
|
366
|
|
|
367
|
Returns pair username, password
|
|
367
|
Returns pair username, password
|
|
368
|
"""
|
|
368
|
"""
|
|
369
|
ui = pwmgr.ui
|
|
369
|
ui = pwmgr.ui
|
|
370
|
after_bad_auth = self._after_bad_auth(ui, realm, authuri, req)
|
|
370
|
after_bad_auth = self._after_bad_auth(ui, realm, authuri, req)
|
|
371
|
|
|
371
|
|
|
372
|
# Look in url, cache, etc
|
|
372
|
# Look in url, cache, etc
|
|
373
|
user, pwd, src, final_url = self.get_credentials(
|
|
373
|
user, pwd, src, final_url = self.get_credentials(
|
|
374
|
pwmgr, realm, authuri, skip_caches=after_bad_auth)
|
|
374
|
pwmgr, realm, authuri, skip_caches=after_bad_auth)
|
|
375
|
if pwd:
|
|
375
|
if pwd:
|
|
376
|
if src != self.SRC_MEMCACHE:
|
|
376
|
if src != self.SRC_MEMCACHE:
|
|
377
|
self.pwd_cache.store(realm, final_url, user, pwd)
|
|
377
|
self.pwd_cache.store(realm, final_url, user, pwd)
|
|
378
|
self._note_last_reply(realm, authuri, user, req)
|
|
378
|
self._note_last_reply(realm, authuri, user, req)
|
|
379
|
_debug(ui, _("Password found in " + src))
|
|
379
|
_debug(ui, _("Password found in " + src))
|
|
380
|
return user, pwd
|
|
380
|
return user, pwd
|
|
381
|
|
|
381
|
|
|
382
|
# Last resort: interactive prompt
|
|
382
|
# Last resort: interactive prompt
|
|
383
|
user, pwd = self.prompt_interactively(ui, user, realm, final_url)
|
|
383
|
user, pwd = self.prompt_interactively(ui, user, realm, final_url)
|
|
384
|
|
|
384
|
|
|
385
|
if user:
|
|
385
|
if user:
|
|
386
|
# Saving password to the keyring.
|
|
386
|
# Saving password to the keyring.
|
|
387
|
# It is done only if username is permanently set.
|
|
387
|
# It is done only if username is permanently set.
|
|
388
|
# Otherwise we won't be able to find the password so it
|
|
388
|
# Otherwise we won't be able to find the password so it
|
|
389
|
# does not make much sense to preserve it
|
|
389
|
# does not make much sense to preserve it
|
|
390
|
_debug(ui, _("Saving password for %s to keyring") % user)
|
|
390
|
_debug(ui, _("Saving password for %s to keyring") % user)
|
|
391
|
password_store.set_http_password(final_url, user, pwd)
|
|
391
|
password_store.set_http_password(final_url, user, pwd)
|
|
392
|
|
|
392
|
|
|
393
|
# Saving password to the memory cache
|
|
393
|
# Saving password to the memory cache
|
|
394
|
self.pwd_cache.store(realm, final_url, user, pwd)
|
|
394
|
self.pwd_cache.store(realm, final_url, user, pwd)
|
|
395
|
self._note_last_reply(realm, authuri, user, req)
|
|
395
|
self._note_last_reply(realm, authuri, user, req)
|
|
396
|
_debug(ui, _("Manually entered password"))
|
|
396
|
_debug(ui, _("Manually entered password"))
|
|
397
|
return user, pwd
|
|
397
|
return user, pwd
|
|
398
|
|
|
398
|
|
|
399
|
def get_url_config(self, ui, parsed_url, user):
|
|
399
|
def get_url_config(self, ui, parsed_url, user):
|
|
400
|
"""
|
|
400
|
"""
|
|
401
|
Checks configuration to decide whether/which username, prefix,
|
|
401
|
Checks configuration to decide whether/which username, prefix,
|
|
402
|
and password are configured for given url. Consults [auth] section.
|
|
402
|
and password are configured for given url. Consults [auth] section.
|
|
403
|
|
|
403
|
|
|
404
|
Returns tuple (username, password, prefix) containing elements
|
|
404
|
Returns tuple (username, password, prefix) containing elements
|
|
405
|
found. username and password can be None (if unset), if prefix
|
|
405
|
found. username and password can be None (if unset), if prefix
|
|
406
|
is not found, url itself is returned.
|
|
406
|
is not found, url itself is returned.
|
|
407
|
"""
|
|
407
|
"""
|
|
408
|
base_url = str(parsed_url)
|
|
408
|
base_url = str(parsed_url)
|
|
409
|
|
|
409
|
|
|
410
|
from mercurial.httpconnection import readauthforuri
|
|
410
|
from mercurial.httpconnection import readauthforuri
|
|
411
|
_debug(ui, _("Checking for hgrc info about url %s, user %s") % (base_url, user))
|
|
411
|
_debug(ui, _("Checking for hgrc info about url %s, user %s") % (base_url, user))
|
|
412
|
res = readauthforuri(ui, base_url, user)
|
|
412
|
res = readauthforuri(ui, base_url, user)
|
|
413
|
# If it user-less version not work, let's try with added username to handle
|
|
413
|
# If it user-less version not work, let's try with added username to handle
|
|
414
|
# both config conventions
|
|
414
|
# both config conventions
|
|
415
|
if (not res) and user:
|
|
415
|
if (not res) and user:
|
|
416
|
parsed_url.user = user
|
|
416
|
parsed_url.user = user
|
|
417
|
res = readauthforuri(ui, str(parsed_url), user)
|
|
417
|
res = readauthforuri(ui, str(parsed_url), user)
|
|
418
|
parsed_url.user = None
|
|
418
|
parsed_url.user = None
|
|
419
|
if res:
|
|
419
|
if res:
|
|
420
|
group, auth_token = res
|
|
420
|
group, auth_token = res
|
|
421
|
else:
|
|
421
|
else:
|
|
422
|
auth_token = None
|
|
422
|
auth_token = None
|
|
423
|
|
|
423
|
|
|
424
|
if auth_token:
|
|
424
|
if auth_token:
|
|
425
|
username = auth_token.get('username')
|
|
425
|
username = auth_token.get('username')
|
|
426
|
password = auth_token.get('password')
|
|
426
|
password = auth_token.get('password')
|
|
427
|
prefix = auth_token.get('prefix')
|
|
427
|
prefix = auth_token.get('prefix')
|
|
428
|
else:
|
|
428
|
else:
|
|
429
|
username = user
|
|
429
|
username = user
|
|
430
|
password = None
|
|
430
|
password = None
|
|
431
|
prefix = None
|
|
431
|
prefix = None
|
|
432
|
|
|
432
|
|
|
433
|
password_url = self.password_url(base_url, prefix)
|
|
433
|
password_url = self.password_url(base_url, prefix)
|
|
434
|
|
|
434
|
|
|
435
|
_debug(ui, _("Password url: %s, user: %s, password: %s (prefix: %s)") % (
|
|
435
|
_debug(ui, _("Password url: %s, user: %s, password: %s (prefix: %s)") % (
|
|
436
|
password_url, username, '********' if password else '', prefix))
|
|
436
|
password_url, username, '********' if password else '', prefix))
|
|
437
|
|
|
437
|
|
|
438
|
return username, password, password_url
|
|
438
|
return username, password, password_url
|
|
439
|
|
|
439
|
|
|
440
|
def _note_last_reply(self, realm, authuri, user, req):
|
|
440
|
def _note_last_reply(self, realm, authuri, user, req):
|
|
441
|
"""
|
|
441
|
"""
|
|
442
|
Internal helper. Saves info about auth-data obtained,
|
|
442
|
Internal helper. Saves info about auth-data obtained,
|
|
443
|
preserves them in last_reply, and returns pair user, pwd
|
|
443
|
preserves them in last_reply, and returns pair user, pwd
|
|
444
|
"""
|
|
444
|
"""
|
|
445
|
self.last_reply = dict(realm=realm, authuri=authuri,
|
|
445
|
self.last_reply = dict(realm=realm, authuri=authuri,
|
|
446
|
user=user, req=req)
|
|
446
|
user=user, req=req)
|
|
447
|
|
|
447
|
|
|
448
|
def _after_bad_auth(self, ui, realm, authuri, req):
|
|
448
|
def _after_bad_auth(self, ui, realm, authuri, req):
|
|
449
|
"""
|
|
449
|
"""
|
|
450
|
If we are called again just after identical previous
|
|
450
|
If we are called again just after identical previous
|
|
451
|
request, then the previously returned auth must have been
|
|
451
|
request, then the previously returned auth must have been
|
|
452
|
wrong. So we note this to force password prompt (and avoid
|
|
452
|
wrong. So we note this to force password prompt (and avoid
|
|
453
|
reusing bad password indefinitely).
|
|
453
|
reusing bad password indefinitely).
|
|
454
|
|
|
454
|
|
|
455
|
This routine checks for this condition.
|
|
455
|
This routine checks for this condition.
|
|
456
|
"""
|
|
456
|
"""
|
|
457
|
if self.last_reply:
|
|
457
|
if self.last_reply:
|
|
458
|
if (self.last_reply['realm'] == realm) \
|
|
458
|
if (self.last_reply['realm'] == realm) \
|
|
459
|
and (self.last_reply['authuri'] == authuri) \
|
|
459
|
and (self.last_reply['authuri'] == authuri) \
|
|
460
|
and (self.last_reply['req'] == req):
|
|
460
|
and (self.last_reply['req'] == req):
|
|
461
|
_debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
|
|
461
|
_debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
|
|
462
|
return True
|
|
462
|
return True
|
|
463
|
return False
|
|
463
|
return False
|
|
464
|
|
|
464
|
|
|
465
|
@staticmethod
|
|
465
|
@staticmethod
|
|
466
|
def password_url(base_url, prefix):
|
|
466
|
def password_url(base_url, prefix):
|
|
467
|
"""Calculates actual url identifying the password. Takes
|
|
467
|
"""Calculates actual url identifying the password. Takes
|
|
468
|
configured prefix under consideration (so can be shorter
|
|
468
|
configured prefix under consideration (so can be shorter
|
|
469
|
than repo url)"""
|
|
469
|
than repo url)"""
|
|
470
|
if not prefix or prefix == '*':
|
|
470
|
if not prefix or prefix == '*':
|
|
471
|
return base_url
|
|
471
|
return base_url
|
|
472
|
scheme, hostpath = base_url.split('://', 1)
|
|
472
|
scheme, hostpath = base_url.split('://', 1)
|
|
473
|
p = prefix.split('://', 1)
|
|
473
|
p = prefix.split('://', 1)
|
|
474
|
if len(p) > 1:
|
|
474
|
if len(p) > 1:
|
|
475
|
prefix_host_path = p[1]
|
|
475
|
prefix_host_path = p[1]
|
|
476
|
else:
|
|
476
|
else:
|
|
477
|
prefix_host_path = prefix
|
|
477
|
prefix_host_path = prefix
|
|
478
|
password_url = scheme + '://' + prefix_host_path
|
|
478
|
password_url = scheme + '://' + prefix_host_path
|
|
479
|
return password_url
|
|
479
|
return password_url
|
|
480
|
|
|
480
|
|
|
481
|
@staticmethod
|
|
481
|
@staticmethod
|
|
482
|
def unpack_url(authuri):
|
|
482
|
def unpack_url(authuri):
|
|
483
|
"""
|
|
483
|
"""
|
|
484
|
Takes original url for which authentication is attempted and:
|
|
484
|
Takes original url for which authentication is attempted and:
|
|
485
|
|
|
485
|
|
|
486
|
- Strips query params from url. Used to convert urls like
|
|
486
|
- Strips query params from url. Used to convert urls like
|
|
487
|
https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
|
|
487
|
https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
|
|
488
|
to
|
|
488
|
to
|
|
489
|
https://repo.machine.com/repos/apps/module
|
|
489
|
https://repo.machine.com/repos/apps/module
|
|
490
|
|
|
490
|
|
|
491
|
- Extracts username and password, if present, and removes them from url
|
|
491
|
- Extracts username and password, if present, and removes them from url
|
|
492
|
(so prefix matching works properly)
|
|
492
|
(so prefix matching works properly)
|
|
493
|
|
|
493
|
|
|
494
|
Returns url, user, password
|
|
494
|
Returns url, user, password
|
|
495
|
where url is mercurial.util.url object already stripped of all those
|
|
495
|
where url is mercurial.util.url object already stripped of all those
|
|
496
|
params.
|
|
496
|
params.
|
|
497
|
"""
|
|
497
|
"""
|
|
498
|
# mercurial.util.url, rather handy url parser
|
|
498
|
# mercurial.util.url, rather handy url parser
|
|
499
|
parsed_url = util.url(authuri)
|
|
499
|
parsed_url = util.url(authuri)
|
|
500
|
parsed_url.query = ''
|
|
500
|
parsed_url.query = ''
|
|
501
|
parsed_url.fragment = None
|
|
501
|
parsed_url.fragment = None
|
|
502
|
# Strip arguments to get actual remote repository url.
|
|
502
|
# Strip arguments to get actual remote repository url.
|
|
503
|
# base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
|
|
503
|
# base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
|
|
504
|
# parsed_url.path)
|
|
504
|
# parsed_url.path)
|
|
505
|
user = parsed_url.user
|
|
505
|
user = parsed_url.user
|
|
506
|
passwd = parsed_url.passwd
|
|
506
|
passwd = parsed_url.passwd
|
|
507
|
parsed_url.user = None
|
|
507
|
parsed_url.user = None
|
|
508
|
parsed_url.passwd = None
|
|
508
|
parsed_url.passwd = None
|
|
509
|
|
|
509
|
|
|
510
|
return parsed_url, user, passwd
|
|
510
|
return parsed_url, user, passwd
|
|
511
|
|
|
511
|
|
|
512
|
|
|
512
|
|
|
513
|
############################################################
|
|
513
|
############################################################
|
|
514
|
# Mercurial monkey-patching
|
|
514
|
# Mercurial monkey-patching
|
|
515
|
############################################################
|
|
515
|
############################################################
|
|
516
|
|
|
516
|
|
|
517
|
|
|
517
|
|
|
518
|
@monkeypatch_method(passwordmgr)
|
|
518
|
@monkeypatch_method(passwordmgr)
|
|
519
|
def find_user_password(self, realm, authuri):
|
|
519
|
def find_user_password(self, realm, authuri):
|
|
520
|
"""
|
|
520
|
"""
|
|
521
|
keyring-based implementation of username/password query
|
|
521
|
keyring-based implementation of username/password query
|
|
522
|
for HTTP(S) connections
|
|
522
|
for HTTP(S) connections
|
|
523
|
|
|
523
|
|
|
524
|
Passwords are saved in gnome keyring, OSX/Chain or other platform
|
|
524
|
Passwords are saved in gnome keyring, OSX/Chain or other platform
|
|
525
|
specific storage and keyed by the repository url
|
|
525
|
specific storage and keyed by the repository url
|
|
526
|
"""
|
|
526
|
"""
|
|
527
|
# Extend object attributes
|
|
527
|
# Extend object attributes
|
|
528
|
if not hasattr(self, '_pwd_handler'):
|
|
528
|
if not hasattr(self, '_pwd_handler'):
|
|
529
|
self._pwd_handler = HTTPPasswordHandler()
|
|
529
|
self._pwd_handler = HTTPPasswordHandler()
|
|
530
|
|
|
530
|
|
|
531
|
if hasattr(self, '_http_req'):
|
|
531
|
if hasattr(self, '_http_req'):
|
|
532
|
req = self._http_req
|
|
532
|
req = self._http_req
|
|
533
|
else:
|
|
533
|
else:
|
|
534
|
req = None
|
|
534
|
req = None
|
|
535
|
|
|
535
|
|
|
536
|
return self._pwd_handler.find_auth(self, realm, authuri, req)
|
|
536
|
return self._pwd_handler.find_auth(self, realm, authuri, req)
|
|
537
|
|
|
537
|
|
|
538
|
|
|
538
|
|
|
539
|
@monkeypatch_method(urllib2.AbstractBasicAuthHandler, "http_error_auth_reqed")
|
|
539
|
@monkeypatch_method(urllib2.AbstractBasicAuthHandler, "http_error_auth_reqed")
|
|
540
|
def basic_http_error_auth_reqed(self, authreq, host, req, headers):
|
|
540
|
def basic_http_error_auth_reqed(self, authreq, host, req, headers):
|
|
541
|
"""Preserves current HTTP request so it can be consulted
|
|
541
|
"""Preserves current HTTP request so it can be consulted
|
|
542
|
in find_user_password above"""
|
|
542
|
in find_user_password above"""
|
|
543
|
self.passwd._http_req = req
|
|
543
|
self.passwd._http_req = req
|
|
544
|
try:
|
|
544
|
try:
|
|
545
|
return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
|
|
545
|
return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
|
|
546
|
finally:
|
|
546
|
finally:
|
|
547
|
self.passwd._http_req = None
|
|
547
|
self.passwd._http_req = None
|
|
548
|
|
|
548
|
|
|
549
|
|
|
549
|
|
|
550
|
@monkeypatch_method(urllib2.AbstractDigestAuthHandler, "http_error_auth_reqed")
|
|
550
|
@monkeypatch_method(urllib2.AbstractDigestAuthHandler, "http_error_auth_reqed")
|
|
551
|
def digest_http_error_auth_reqed(self, authreq, host, req, headers):
|
|
551
|
def digest_http_error_auth_reqed(self, authreq, host, req, headers):
|
|
552
|
"""Preserves current HTTP request so it can be consulted
|
|
552
|
"""Preserves current HTTP request so it can be consulted
|
|
553
|
in find_user_password above"""
|
|
553
|
in find_user_password above"""
|
|
554
|
self.passwd._http_req = req
|
|
554
|
self.passwd._http_req = req
|
|
555
|
try:
|
|
555
|
try:
|
|
556
|
return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
|
|
556
|
return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
|
|
557
|
finally:
|
|
557
|
finally:
|
|
558
|
self.passwd._http_req = None
|
|
558
|
self.passwd._http_req = None
|
|
559
|
|
|
559
|
|
|
560
|
############################################################
|
|
560
|
############################################################
|
|
561
|
# SMTP support
|
|
561
|
# SMTP support
|
|
562
|
############################################################
|
|
562
|
############################################################
|
|
563
|
|
|
563
|
|
|
564
|
|
|
564
|
|
|
565
|
def try_smtp_login(ui, smtp_obj, username, password):
|
|
565
|
def try_smtp_login(ui, smtp_obj, username, password):
|
|
566
|
"""
|
|
566
|
"""
|
|
567
|
Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
|
|
567
|
Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
|
|
568
|
password.
|
|
568
|
password.
|
|
569
|
|
|
569
|
|
|
570
|
Returns:
|
|
570
|
Returns:
|
|
571
|
- True if login succeeded
|
|
571
|
- True if login succeeded
|
|
572
|
- False if login failed due to the wrong credentials
|
|
572
|
- False if login failed due to the wrong credentials
|
|
573
|
|
|
573
|
|
|
574
|
Throws Abort exception if login failed for any other reason.
|
|
574
|
Throws Abort exception if login failed for any other reason.
|
|
575
|
|
|
575
|
|
|
576
|
Immediately returns False if password is empty
|
|
576
|
Immediately returns False if password is empty
|
|
577
|
"""
|
|
577
|
"""
|
|
578
|
if not password:
|
|
578
|
if not password:
|
|
579
|
return False
|
|
579
|
return False
|
|
580
|
try:
|
|
580
|
try:
|
|
581
|
ui.note(_('(authenticating to mail server as %s)\n') %
|
|
581
|
ui.note(_('(authenticating to mail server as %s)\n') %
|
|
582
|
(username))
|
|
582
|
(username))
|
|
583
|
smtp_obj.login(username, password)
|
|
583
|
smtp_obj.login(username, password)
|
|
584
|
return True
|
|
584
|
return True
|
|
585
|
except smtplib.SMTPException, inst:
|
|
585
|
except smtplib.SMTPException, inst:
|
|
586
|
if inst.smtp_code == 535:
|
|
586
|
if inst.smtp_code == 535:
|
|
587
|
ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
|
|
587
|
ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
|
|
588
|
return False
|
|
588
|
return False
|
|
589
|
else:
|
|
589
|
else:
|
|
590
|
raise util.Abort(inst)
|
|
590
|
raise util.Abort(inst)
|
|
591
|
|
|
591
|
|
|
592
|
|
|
592
|
|
|
593
|
def keyring_supported_smtp(ui, username):
|
|
593
|
def keyring_supported_smtp(ui, username):
|
|
594
|
"""
|
|
594
|
"""
|
|
595
|
keyring-integrated replacement for mercurial.mail._smtp
|
|
595
|
keyring-integrated replacement for mercurial.mail._smtp
|
|
596
|
Used only when configuration file contains username, but
|
|
596
|
Used only when configuration file contains username, but
|
|
597
|
does not contain the password.
|
|
597
|
does not contain the password.
|
|
598
|
|
|
598
|
|
|
599
|
Most of the routine below is copied as-is from
|
|
599
|
Most of the routine below is copied as-is from
|
|
600
|
mercurial.mail._smtp. The only changed part is
|
|
600
|
mercurial.mail._smtp. The only changed part is
|
|
601
|
marked with # >>>>> and # <<<<< markers
|
|
601
|
marked with # >>>>> and # <<<<< markers
|
|
602
|
"""
|
|
602
|
"""
|
|
603
|
local_hostname = ui.config('smtp', 'local_hostname')
|
|
603
|
local_hostname = ui.config('smtp', 'local_hostname')
|
|
604
|
tls = ui.config('smtp', 'tls', 'none')
|
|
604
|
tls = ui.config('smtp', 'tls', 'none')
|
|
605
|
# backward compatible: when tls = true, we use starttls.
|
|
605
|
# backward compatible: when tls = true, we use starttls.
|
|
606
|
starttls = tls == 'starttls' or util.parsebool(tls)
|
|
606
|
starttls = tls == 'starttls' or util.parsebool(tls)
|
|
607
|
smtps = tls == 'smtps'
|
|
607
|
smtps = tls == 'smtps'
|
|
608
|
if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
|
|
608
|
if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
|
|
609
|
raise util.Abort(_("can't use TLS: Python SSL support not installed"))
|
|
609
|
raise util.Abort(_("can't use TLS: Python SSL support not installed"))
|
|
610
|
mailhost = ui.config('smtp', 'host')
|
|
610
|
mailhost = ui.config('smtp', 'host')
|
|
611
|
if not mailhost:
|
|
611
|
if not mailhost:
|
|
612
|
raise util.Abort(_('smtp.host not configured - cannot send mail'))
|
|
612
|
raise util.Abort(_('smtp.host not configured - cannot send mail'))
|
|
613
|
verifycert = ui.config('smtp', 'verifycert', 'strict')
|
|
613
|
verifycert = ui.config('smtp', 'verifycert', 'strict')
|
|
614
|
if verifycert not in ['strict', 'loose']:
|
|
614
|
if verifycert not in ['strict', 'loose']:
|
|
615
|
if util.parsebool(verifycert) is not False:
|
|
615
|
if util.parsebool(verifycert) is not False:
|
|
616
|
raise util.Abort(_('invalid smtp.verifycert configuration: %s')
|
|
616
|
raise util.Abort(_('invalid smtp.verifycert configuration: %s')
|
|
617
|
% (verifycert))
|
|
617
|
% (verifycert))
|
|
618
|
verifycert = False
|
|
618
|
verifycert = False
|
|
619
|
if (starttls or smtps) and verifycert:
|
|
619
|
if (starttls or smtps) and verifycert:
|
|
620
|
sslkwargs = sslutil.sslkwargs(ui, mailhost)
|
|
620
|
sslkwargs = sslutil.sslkwargs(ui, mailhost)
|
|
621
|
else:
|
|
621
|
else:
|
|
622
|
sslkwargs = {}
|
|
622
|
sslkwargs = {}
|
|
623
|
if smtps:
|
|
623
|
if smtps:
|
|
624
|
ui.note(_('(using smtps)\n'))
|
|
624
|
ui.note(_('(using smtps)\n'))
|
|
625
|
s = SMTPS(sslkwargs, local_hostname=local_hostname)
|
|
625
|
|
|
|
|
|
626
|
# mercurial 3.8 added a mandatory host arg
|
|
|
|
|
627
|
if 'host' in SMTPS.__init__.__code__.co_varnames:
|
|
|
|
|
628
|
s = SMTPS(sslkwargs, local_hostname=local_hostname, host=mailhost)
|
|
|
|
|
629
|
else:
|
|
|
|
|
630
|
s = SMTPS(sslkwargs, local_hostname=local_hostname)
|
|
626
|
elif starttls:
|
|
631
|
elif starttls:
|
|
627
|
s = STARTTLS(sslkwargs, local_hostname=local_hostname)
|
|
632
|
if 'host' in STARTTLS.__init__.__code__.co_varnames:
|
|
|
|
|
633
|
s = STARTTLS(sslkwargs, local_hostname=local_hostname, host=mailhost)
|
|
|
|
|
634
|
else:
|
|
|
|
|
635
|
s = STARTTLS(sslkwargs, local_hostname=local_hostname)
|
|
628
|
else:
|
|
636
|
else:
|
|
629
|
s = smtplib.SMTP(local_hostname=local_hostname)
|
|
637
|
s = smtplib.SMTP(local_hostname=local_hostname)
|
|
630
|
if smtps:
|
|
638
|
if smtps:
|
|
631
|
defaultport = 465
|
|
639
|
defaultport = 465
|
|
632
|
else:
|
|
640
|
else:
|
|
633
|
defaultport = 25
|
|
641
|
defaultport = 25
|
|
634
|
mailport = util.getport(ui.config('smtp', 'port', defaultport))
|
|
642
|
mailport = util.getport(ui.config('smtp', 'port', defaultport))
|
|
635
|
ui.note(_('sending mail: smtp host %s, port %s\n') %
|
|
643
|
ui.note(_('sending mail: smtp host %s, port %s\n') %
|
|
636
|
(mailhost, mailport))
|
|
644
|
(mailhost, mailport))
|
|
637
|
s.connect(host=mailhost, port=mailport)
|
|
645
|
s.connect(host=mailhost, port=mailport)
|
|
638
|
if starttls:
|
|
646
|
if starttls:
|
|
639
|
ui.note(_('(using starttls)\n'))
|
|
647
|
ui.note(_('(using starttls)\n'))
|
|
640
|
s.ehlo()
|
|
648
|
s.ehlo()
|
|
641
|
s.starttls()
|
|
649
|
s.starttls()
|
|
642
|
s.ehlo()
|
|
650
|
s.ehlo()
|
|
643
|
if (starttls or smtps) and verifycert:
|
|
651
|
if (starttls or smtps) and verifycert:
|
|
644
|
ui.note(_('(verifying remote certificate)\n'))
|
|
652
|
ui.note(_('(verifying remote certificate)\n'))
|
|
645
|
sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
|
|
653
|
sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
|
|
646
|
|
|
654
|
|
|
647
|
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
655
|
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
648
|
stored = password = password_store.get_smtp_password(
|
|
656
|
stored = password = password_store.get_smtp_password(
|
|
649
|
mailhost, mailport, username)
|
|
657
|
mailhost, mailport, username)
|
|
650
|
# No need to check whether password was found as try_smtp_login
|
|
658
|
# No need to check whether password was found as try_smtp_login
|
|
651
|
# just returns False if it is absent.
|
|
659
|
# just returns False if it is absent.
|
|
652
|
while not try_smtp_login(ui, s, username, password):
|
|
660
|
while not try_smtp_login(ui, s, username, password):
|
|
653
|
password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
|
|
661
|
password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
|
|
654
|
|
|
662
|
|
|
655
|
if stored != password:
|
|
663
|
if stored != password:
|
|
656
|
password_store.set_smtp_password(
|
|
664
|
password_store.set_smtp_password(
|
|
657
|
mailhost, mailport, username, password)
|
|
665
|
mailhost, mailport, username, password)
|
|
658
|
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
666
|
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
659
|
|
|
667
|
|
|
660
|
def send(sender, recipients, msg):
|
|
668
|
def send(sender, recipients, msg):
|
|
661
|
try:
|
|
669
|
try:
|
|
662
|
return s.sendmail(sender, recipients, msg)
|
|
670
|
return s.sendmail(sender, recipients, msg)
|
|
663
|
except smtplib.SMTPRecipientsRefused, inst:
|
|
671
|
except smtplib.SMTPRecipientsRefused, inst:
|
|
664
|
recipients = [r[1] for r in inst.recipients.values()]
|
|
672
|
recipients = [r[1] for r in inst.recipients.values()]
|
|
665
|
raise util.Abort('\n' + '\n'.join(recipients))
|
|
673
|
raise util.Abort('\n' + '\n'.join(recipients))
|
|
666
|
except smtplib.SMTPException, inst:
|
|
674
|
except smtplib.SMTPException, inst:
|
|
667
|
raise util.Abort(inst)
|
|
675
|
raise util.Abort(inst)
|
|
668
|
|
|
676
|
|
|
669
|
return send
|
|
677
|
return send
|
|
670
|
|
|
678
|
|
|
671
|
############################################################
|
|
679
|
############################################################
|
|
672
|
# SMTP monkeypatching
|
|
680
|
# SMTP monkeypatching
|
|
673
|
############################################################
|
|
681
|
############################################################
|
|
674
|
|
|
682
|
|
|
675
|
|
|
683
|
|
|
676
|
@monkeypatch_method(mail)
|
|
684
|
@monkeypatch_method(mail)
|
|
677
|
def _smtp(ui):
|
|
685
|
def _smtp(ui):
|
|
678
|
"""
|
|
686
|
"""
|
|
679
|
build an smtp connection and return a function to send email
|
|
687
|
build an smtp connection and return a function to send email
|
|
680
|
|
|
688
|
|
|
681
|
This is the monkeypatched version of _smtp(ui) function from
|
|
689
|
This is the monkeypatched version of _smtp(ui) function from
|
|
682
|
mercurial/mail.py. It calls the original unless username
|
|
690
|
mercurial/mail.py. It calls the original unless username
|
|
683
|
without password is given in the configuration.
|
|
691
|
without password is given in the configuration.
|
|
684
|
"""
|
|
692
|
"""
|
|
685
|
username = ui.config('smtp', 'username')
|
|
693
|
username = ui.config('smtp', 'username')
|
|
686
|
password = ui.config('smtp', 'password')
|
|
694
|
password = ui.config('smtp', 'password')
|
|
687
|
|
|
695
|
|
|
688
|
if username and not password:
|
|
696
|
if username and not password:
|
|
689
|
return keyring_supported_smtp(ui, username)
|
|
697
|
return keyring_supported_smtp(ui, username)
|
|
690
|
else:
|
|
698
|
else:
|
|
691
|
return _smtp.orig(ui)
|
|
699
|
return _smtp.orig(ui)
|
|
692
|
|
|
700
|
|
|
693
|
|
|
701
|
|
|
694
|
############################################################
|
|
702
|
############################################################
|
|
695
|
# Custom commands
|
|
703
|
# Custom commands
|
|
696
|
############################################################
|
|
704
|
############################################################
|
|
697
|
|
|
705
|
|
|
698
|
cmdtable = {}
|
|
706
|
cmdtable = {}
|
|
699
|
command = meu.command(cmdtable)
|
|
707
|
command = meu.command(cmdtable)
|
|
700
|
|
|
708
|
|
|
701
|
|
|
709
|
|
|
702
|
@command('keyring_check',
|
|
710
|
@command('keyring_check',
|
|
703
|
[],
|
|
711
|
[],
|
|
704
|
_("keyring_check [PATH]"),
|
|
712
|
_("keyring_check [PATH]"),
|
|
705
|
optionalrepo=True)
|
|
713
|
optionalrepo=True)
|
|
706
|
def cmd_keyring_check(ui, repo, *path_args, **opts): # pylint: disable=unused-argument
|
|
714
|
def cmd_keyring_check(ui, repo, *path_args, **opts): # pylint: disable=unused-argument
|
|
707
|
"""
|
|
715
|
"""
|
|
708
|
Prints basic info (whether password is currently saved, and how is
|
|
716
|
Prints basic info (whether password is currently saved, and how is
|
|
709
|
it identified) for given path.
|
|
717
|
it identified) for given path.
|
|
710
|
|
|
718
|
|
|
711
|
Can be run without parameters to show status for all (current repository) paths which
|
|
719
|
Can be run without parameters to show status for all (current repository) paths which
|
|
712
|
are HTTP-like.
|
|
720
|
are HTTP-like.
|
|
713
|
"""
|
|
721
|
"""
|
|
714
|
defined_paths = [(name, url)
|
|
722
|
defined_paths = [(name, url)
|
|
715
|
for name, url in ui.configitems('paths')]
|
|
723
|
for name, url in ui.configitems('paths')]
|
|
716
|
if path_args:
|
|
724
|
if path_args:
|
|
717
|
# Maybe parameter is an alias
|
|
725
|
# Maybe parameter is an alias
|
|
718
|
defined_paths_dic = dict(defined_paths)
|
|
726
|
defined_paths_dic = dict(defined_paths)
|
|
719
|
paths = [(path_arg, defined_paths_dic.get(path_arg, path_arg))
|
|
727
|
paths = [(path_arg, defined_paths_dic.get(path_arg, path_arg))
|
|
720
|
for path_arg in path_args]
|
|
728
|
for path_arg in path_args]
|
|
721
|
else:
|
|
729
|
else:
|
|
722
|
if not repo:
|
|
730
|
if not repo:
|
|
723
|
ui.status(_("Url to check not specified. Either run ``hg keyring_check https://...``, or run the command inside some repository (to test all defined paths).\n"))
|
|
731
|
ui.status(_("Url to check not specified. Either run ``hg keyring_check https://...``, or run the command inside some repository (to test all defined paths).\n"))
|
|
724
|
return
|
|
732
|
return
|
|
725
|
paths = [(name, url) for name, url in defined_paths]
|
|
733
|
paths = [(name, url) for name, url in defined_paths]
|
|
726
|
|
|
734
|
|
|
727
|
if not paths:
|
|
735
|
if not paths:
|
|
728
|
ui.status(_("keyring_check: no paths defined\n"))
|
|
736
|
ui.status(_("keyring_check: no paths defined\n"))
|
|
729
|
return
|
|
737
|
return
|
|
730
|
|
|
738
|
|
|
731
|
handler = HTTPPasswordHandler()
|
|
739
|
handler = HTTPPasswordHandler()
|
|
732
|
|
|
740
|
|
|
733
|
ui.status(_("keyring password save status:\n"))
|
|
741
|
ui.status(_("keyring password save status:\n"))
|
|
734
|
for name, url in paths:
|
|
742
|
for name, url in paths:
|
|
735
|
if not is_http_path(url):
|
|
743
|
if not is_http_path(url):
|
|
736
|
if path_args:
|
|
744
|
if path_args:
|
|
737
|
ui.status(_(" %s: non-http path (%s)\n") % (name, url))
|
|
745
|
ui.status(_(" %s: non-http path (%s)\n") % (name, url))
|
|
738
|
continue
|
|
746
|
continue
|
|
739
|
user, pwd, source, final_url = handler.get_credentials(
|
|
747
|
user, pwd, source, final_url = handler.get_credentials(
|
|
740
|
passwordmgr(ui), name, url)
|
|
748
|
passwordmgr(ui), name, url)
|
|
741
|
if pwd:
|
|
749
|
if pwd:
|
|
742
|
ui.status(_(" %s: password available, source: %s, bound to user %s, url %s\n") % (
|
|
750
|
ui.status(_(" %s: password available, source: %s, bound to user %s, url %s\n") % (
|
|
743
|
name, source, user, final_url))
|
|
751
|
name, source, user, final_url))
|
|
744
|
elif user:
|
|
752
|
elif user:
|
|
745
|
ui.status(_(" %s: password not available, once entered, will be bound to user %s, url %s\n") % (
|
|
753
|
ui.status(_(" %s: password not available, once entered, will be bound to user %s, url %s\n") % (
|
|
746
|
name, user, final_url))
|
|
754
|
name, user, final_url))
|
|
747
|
else:
|
|
755
|
else:
|
|
748
|
ui.status(_(" %s: password not available, user unknown, url %s\n") % (
|
|
756
|
ui.status(_(" %s: password not available, user unknown, url %s\n") % (
|
|
749
|
name, final_url))
|
|
757
|
name, final_url))
|
|
750
|
|
|
758
|
|
|
751
|
|
|
759
|
|
|
752
|
@command('keyring_clear',
|
|
760
|
@command('keyring_clear',
|
|
753
|
[],
|
|
761
|
[],
|
|
754
|
_('hg keyring_clear PATH-OR-ALIAS'),
|
|
762
|
_('hg keyring_clear PATH-OR-ALIAS'),
|
|
755
|
optionalrepo=True)
|
|
763
|
optionalrepo=True)
|
|
756
|
def cmd_keyring_clear(ui, repo, path, **opts): # pylint: disable=unused-argument
|
|
764
|
def cmd_keyring_clear(ui, repo, path, **opts): # pylint: disable=unused-argument
|
|
757
|
"""
|
|
765
|
"""
|
|
758
|
Drops password bound to given path (if any is saved).
|
|
766
|
Drops password bound to given path (if any is saved).
|
|
759
|
|
|
767
|
|
|
760
|
Parameter can be given as full url (``https://John@bitbucket.org``) or as the name
|
|
768
|
Parameter can be given as full url (``https://John@bitbucket.org``) or as the name
|
|
761
|
of path alias (``bitbucket``).
|
|
769
|
of path alias (``bitbucket``).
|
|
762
|
"""
|
|
770
|
"""
|
|
763
|
path_url = path
|
|
771
|
path_url = path
|
|
764
|
for name, url in ui.configitems('paths'):
|
|
772
|
for name, url in ui.configitems('paths'):
|
|
765
|
if name == path:
|
|
773
|
if name == path:
|
|
766
|
path_url = url
|
|
774
|
path_url = url
|
|
767
|
break
|
|
775
|
break
|
|
768
|
if not is_http_path(path_url):
|
|
776
|
if not is_http_path(path_url):
|
|
769
|
ui.status(_("%s is not a http path (and %s can't be resolved as path alias)\n") % (path, path_url))
|
|
777
|
ui.status(_("%s is not a http path (and %s can't be resolved as path alias)\n") % (path, path_url))
|
|
770
|
return
|
|
778
|
return
|
|
771
|
|
|
779
|
|
|
772
|
handler = HTTPPasswordHandler()
|
|
780
|
handler = HTTPPasswordHandler()
|
|
773
|
|
|
781
|
|
|
774
|
user, pwd, source, final_url = handler.get_credentials(
|
|
782
|
user, pwd, source, final_url = handler.get_credentials(
|
|
775
|
passwordmgr(ui), path, path_url)
|
|
783
|
passwordmgr(ui), path, path_url)
|
|
776
|
if not user:
|
|
784
|
if not user:
|
|
777
|
ui.status(_("Username not configured for url %s\n") % final_url)
|
|
785
|
ui.status(_("Username not configured for url %s\n") % final_url)
|
|
778
|
return
|
|
786
|
return
|
|
779
|
if not pwd:
|
|
787
|
if not pwd:
|
|
780
|
ui.status(_("No password is saved for user %s, url %s\n") % (
|
|
788
|
ui.status(_("No password is saved for user %s, url %s\n") % (
|
|
781
|
user, final_url))
|
|
789
|
user, final_url))
|
|
782
|
return
|
|
790
|
return
|
|
783
|
|
|
791
|
|
|
784
|
if source != handler.SRC_KEYRING:
|
|
792
|
if source != handler.SRC_KEYRING:
|
|
785
|
ui.status(_("Password for user %s, url %s is saved in %s, not in keyring\n") % (
|
|
793
|
ui.status(_("Password for user %s, url %s is saved in %s, not in keyring\n") % (
|
|
786
|
user, final_url, source))
|
|
794
|
user, final_url, source))
|
|
787
|
|
|
795
|
|
|
788
|
password_store.clear_http_password(final_url, user)
|
|
796
|
password_store.clear_http_password(final_url, user)
|
|
789
|
ui.status(_("Password removed for user %s, url %s\n") % (
|
|
797
|
ui.status(_("Password removed for user %s, url %s\n") % (
|
|
790
|
user, final_url))
|
|
798
|
user, final_url))
|
|
791
|
|
|
799
|
|
|
792
|
|
|
800
|
|
|
793
|
buglink = 'https://bitbucket.org/Mekk/mercurial_keyring/issues'
|
|
801
|
buglink = 'https://bitbucket.org/Mekk/mercurial_keyring/issues'
|