Commit 36cbcbf7 authored by Steve Borho's avatar Steve Borho
Browse files

sync: simplify authentication dialog, rename to 'Security'

This dialog configures both https connection properties and user authentication.
It no longer offers the full feature set of the [auth] section as the complexity
is much too great for what is a rare requirement.  Also the previous logic was
defaulting to allowing authentication over http, which was transmitting your
password in plaintext if you happened to accidentally push over http.

It also disables the password entry if mercurial_keyring is enabled, since it
completely destroys the usefulness of mercurial_keyring if you go and save your
password in plaintext for zero benefit.

More work remains to be done, we need to add --insecure to the command line if
the hostname is found in [insecurehosts] and we are using https.
parent 8a0c060855e0
......@@ -146,8 +146,8 @@ class SyncWidget(QWidget):
self.pathentry.setAcceptDrops(False)
self.pathentry.textChanged.connect(self.refreshUrl)
hbox.addWidget(self.pathentry, 1)
self.authbutton = QPushButton(_('Authentication'))
hbox.addWidget(self.authbutton)
self.securebutton = QPushButton(_('Security'))
hbox.addWidget(self.securebutton)
self.savebutton = QPushButton(_('Save'))
hbox.addWidget(self.savebutton)
self.layout().addLayout(hbox)
......@@ -186,7 +186,7 @@ class SyncWidget(QWidget):
self.layout().addLayout(hbox, 1)
self.savebutton.clicked.connect(self.saveclicked)
self.authbutton.clicked.connect(self.authclicked)
self.securebutton.clicked.connect(self.secureclicked)
self.postpullbutton.clicked.connect(self.postpullclicked)
self.optionsbutton.pressed.connect(self.editOptions)
......@@ -311,7 +311,7 @@ class SyncWidget(QWidget):
schemeIndex = self.schemecombo.currentIndex()
self.hostentry.setEnabled(schemeIndex != 0)
self.portentry.setEnabled(schemeIndex != 0)
self.authbutton.setEnabled(schemeIndex > 1)
self.securebutton.setEnabled(schemeIndex > 1)
def currentUrl(self, hidepw):
scheme = _schemes[self.schemecombo.currentIndex()]
......@@ -482,11 +482,11 @@ class SyncWidget(QWidget):
if dlg.exec_() == QDialog.Accepted:
self.curalias = hglib.fromunicode(dlg.aliasentry.text())
def authclicked(self):
def secureclicked(self):
host = hglib.fromunicode(self.hostentry.text())
user = self.curuser or ''
pw = self.curpw or ''
dlg = AuthDialog(self.repo, host, user, pw, self)
dlg = SecureDialog(self.repo, host, user, pw, self)
dlg.setWindowFlags(Qt.Sheet)
dlg.setWindowModality(Qt.WindowModal)
if dlg.exec_() == QDialog.Accepted:
......@@ -941,37 +941,74 @@ class SaveDialog(QDialog):
def reject(self):
super(SaveDialog, self).reject()
class AuthDialog(QDialog):
class SecureDialog(QDialog):
def __init__(self, repo, host, user, pw, parent):
super(AuthDialog, self).__init__(parent)
super(SecureDialog, self).__init__(parent)
def genfingerprint():
pem = ssl.get_server_certificate( (host, 443) )
der = ssl.PEM_cert_to_DER_cert(pem)
hash = util.sha1(der).hexdigest()
pretty = ":".join([hash[x:x + 2] for x in xrange(0, len(hash), 2)])
le.setText(pretty)
uhost = hglib.tounicode(host)
self.setWindowTitle(_('Authentication: ') + uhost)
self.setWindowTitle(_('Security: ') + uhost)
self.setWindowFlags(self.windowFlags() & \
~Qt.WindowContextHelpButtonHint)
self.repo = repo
self.host = host
self.alias = host.replace('.', '')
self.setLayout(QVBoxLayout())
securebox = QGroupBox(_('Secure HTTPS Connection'))
self.layout().addWidget(securebox)
vbox = QVBoxLayout()
securebox.setLayout(vbox)
self.layout().addWidget(securebox)
self.cacertradio = QRadioButton(
_('Verify with Certificate Authority certificates (best)'))
self.fprintradio = QRadioButton(
_('Verify with stored host fingerprint (good)'))
self.insecureradio = QRadioButton(
_('No host validation, but still encrypted (bad)'))
hbox = QHBoxLayout()
fprint = repo.ui.config('hostfingerprints', host, '')
self.fprintentry = le = QLineEdit(fprint)
self.fprintradio.toggled.connect(self.fprintentry.setEnabled)
self.fprintentry.setEnabled(False)
if hasattr(le, 'setPlaceholderText'): # Qt >= 4.7
le.setPlaceholderText('### host certificate fingerprint ###')
hbox.addWidget(le)
try:
import ssl # Python 2.6 or backport for 2.5
qb = QPushButton(_('Query'))
qb.clicked.connect(genfingerprint)
qb.setEnabled(False)
self.fprintradio.toggled.connect(qb.setEnabled)
hbox.addWidget(qb)
except ImportError:
pass
vbox.addWidget(self.cacertradio)
vbox.addWidget(self.fprintradio)
vbox.addLayout(hbox)
vbox.addWidget(self.insecureradio)
self.cacertradio.setEnabled(bool(repo.ui.config('web', 'cacerts')))
self.cacertradio.setChecked(True) # default
if fprint:
self.fprintradio.setChecked(True)
elif repo.ui.config('insecurehosts', host):
self.insecureradio.setChecked(True)
authbox = QGroupBox(_('User Authentication'))
self.layout().addWidget(authbox)
form = QFormLayout()
authbox.setLayout(form)
self.aliasentry = QLineEdit(host.split('.', 1)[0])
form.addRow(_('Site Alias'), self.aliasentry)
self.schemes = QComboBox()
self.schemes.addItems(('http https', 'http', 'https'))
form.addRow(_('Schemes'), self.schemes)
self.prefixentry = QLineEdit(host)
self.prefixentry.setToolTip(
_('''Either * or a URI prefix with or without the scheme part. The
authentication entry with the longest matching prefix is used (where * matches
everything and counts as a match of length 1). If the prefix doesn't include
a scheme, the match is performed against the URI with its scheme stripped as
well, and the schemes argument, q.v., is then subsequently consulted.'''))
form.addRow(_('Prefix'), self.prefixentry)
self.userentry = QLineEdit(user)
self.layout().addWidget(authbox)
cfg = repo.ui.config('auth', self.alias+'.username', '')
self.userentry = QLineEdit(user or cfg)
self.userentry.setToolTip(
_('''Optional. Username to authenticate with. If not given, and the remote
site requires basic or digest authentication, the user will be prompted for
......@@ -979,58 +1016,35 @@ it. Environment variables are expanded in the username letting you do
foo.username = $USER.'''))
form.addRow(_('Username'), self.userentry)
self.pwentry = QLineEdit(pw)
cfg = repo.ui.config('auth', self.alias+'.password', '')
self.pwentry = QLineEdit(pw or cfg)
self.pwentry.setEchoMode(QLineEdit.Password)
self.pwentry.setToolTip(
_('''Optional. Password to authenticate with. If not given, and the remote
site requires basic or digest authentication, the user will be prompted for
it.'''))
form.addRow(_('Password'), self.pwentry)
self.keyentry = QLineEdit()
if 'mercurial_keyring' in repo.extensions():
self.pwentry.clear()
self.pwentry.setEnabled(False)
self.pwentry.setToolTip(_('Mercurial keyring extension is enabled. '
'Passwords will be stored in platform native '
'cryptographically secure method.'))
cfg = repo.ui.config('auth', self.alias+'.key', '')
self.keyentry = QLineEdit(cfg)
self.keyentry.setToolTip(
_('''Optional. PEM encoded client certificate key file. Environment variables
are expanded in the filename.'''))
form.addRow(_('User Certificate Key File'), self.keyentry)
self.chainentry = QLineEdit()
cfg = repo.ui.config('auth', self.alias+'.cert', '')
self.chainentry = QLineEdit(cfg)
self.chainentry.setToolTip(
_('''Optional. PEM encoded client certificate chain file. Environment variables
are expanded in the filename.'''))
form.addRow(_('User Certificate Chain File'), self.chainentry)
txt = _('Authentication section %smanual page%s') % (
'<a href="http://www.selenic.com/mercurial/hgrc.5.html#auth">',
'</a>')
self.lbl = QLabel(txt)
self.lbl.setOpenExternalLinks(True)
form.addRow(self.lbl, None)
def genfingerprint():
pem = ssl.get_server_certificate( (host, 443) )
der = ssl.PEM_cert_to_DER_cert(pem)
hash = util.sha1(der).hexdigest()
pretty = ":".join([hash[x:x + 2] for x in xrange(0, len(hash), 2)])
le.setText(pretty)
hostbox = QGroupBox(_('%s host certificate fingerprint') % uhost)
self.layout().addWidget(hostbox)
hbox = QHBoxLayout()
hostbox.setLayout(hbox)
self.host = host
cur = self.repo.ui.config('hostfingerprints', host, '')
self.fingerprintentry = le = QLineEdit(cur)
if hasattr(le, 'setPlaceholderText'): # Qt >= 4.7
le.setPlaceholderText('### host certificate fingerprint ###')
hbox.addWidget(le)
try:
import ssl # Python 2.6 or backport for 2.5
qb = QPushButton(_('Query'))
qb.clicked.connect(genfingerprint)
hbox.addWidget(qb)
except ImportError:
pass
BB = QDialogButtonBox
bb = QDialogButtonBox(BB.Help|BB.Save|BB.Cancel)
bb.rejected.connect(self.reject)
......@@ -1054,36 +1068,35 @@ are expanded in the filename.'''))
return
if fn is None:
return
schemes = hglib.fromunicode(self.schemes.currentText())
prefix = hglib.fromunicode(self.prefixentry.text())
def setorclear(section, item, value):
if value:
cfg.set(section, item, value)
elif not value and item in cfg[section]:
del cfg[section][item]
if self.cacertradio.isChecked():
fprint = None
insecure = None
elif self.fprintradio.isChecked():
fprint = hglib.fromunicode(self.fprintentry.text())
insecure = None
else:
fprint = None
insecure = '1'
setorclear('hostfingerprints', self.host, fprint)
setorclear('insecurehosts', self.host, insecure)
username = hglib.fromunicode(self.userentry.text())
password = hglib.fromunicode(self.pwentry.text())
key = hglib.fromunicode(self.keyentry.text())
chain = hglib.fromunicode(self.chainentry.text())
alias = hglib.fromunicode(self.aliasentry.text())
if alias+'.prefix' in cfg['auth']:
if not qtlib.QuestionMsgBox(_('Confirm authentication replace'),
_('Authentication info for %s already '
'exists, replace?') % alias):
return
cfg.set('auth', alias+'.schemes', schemes)
if username:
cfg.set('auth', alias+'.username', username)
if prefix:
cfg.set('auth', alias+'.prefix', prefix)
def setorclear(item, value):
item = '.'.join([alias, item])
if value:
cfg.set('auth', item, value)
elif not value and item in cfg['auth']:
del cfg['auth'][item]
setorclear('password', password)
setorclear('key', key)
setorclear('cert', chain)
fprint = hglib.fromunicode(self.fingerprintentry.text())
if fprint:
cfg.set('hostfingerprints', self.host, fprint)
cfg.set('auth', self.alias+'.prefix', self.host)
setorclear('auth', self.alias+'.username', username)
setorclear('auth', self.alias+'.password', password)
setorclear('auth', self.alias+'.key', key)
setorclear('auth', self.alias+'.cert', chain)
self.repo.incrementBusyCount()
try:
......@@ -1092,10 +1105,10 @@ are expanded in the filename.'''))
qtlib.WarningMsgBox(_('Unable to write configuration file'),
hglib.tounicode(e), parent=self)
self.repo.decrementBusyCount()
super(AuthDialog, self).accept()
super(SecureDialog, self).accept()
def reject(self):
super(AuthDialog, self).reject()
super(SecureDialog, self).reject()
class PathsTree(QTreeView):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment