QGIS API Documentation  3.14.0-Pi (9f7028fd23)
qgsauthsslimportdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsauthsslimportdialog.cpp
3  ---------------------
4  begin : May 17, 2015
5  copyright : (C) 2015 by Boundless Spatial, Inc. USA
6  author : Larry Shaffer
7  email : lshaffer at boundlessgeo dot com
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 /****************************************************************************
18 **
19 ** Portions of this code were derived from the following...
20 **
21 ** qt-everywhere-opensource-src-4.8.6/examples/network/
22 ** securesocketclient/certificateinfo.h (and .cpp)
23 **
24 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
25 ** Contact: http://www.qt-project.org/legal
26 **
27 ** This file is part of the examples of the Qt Toolkit.
28 **
29 ** $QT_BEGIN_LICENSE:BSD$
30 ** You may use this file under the terms of the BSD license as follows:
31 **
32 ** "Redistribution and use in source and binary forms, with or without
33 ** modification, are permitted provided that the following conditions are
34 ** met:
35 ** * Redistributions of source code must retain the above copyright
36 ** notice, this list of conditions and the following disclaimer.
37 ** * Redistributions in binary form must reproduce the above copyright
38 ** notice, this list of conditions and the following disclaimer in
39 ** the documentation and/or other materials provided with the
40 ** distribution.
41 ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
42 ** of its contributors may be used to endorse or promote products derived
43 ** from this software without specific prior written permission.
44 **
45 **
46 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
47 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
48 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
49 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
50 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
51 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
52 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
53 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
54 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
55 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
56 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
57 **
58 ** $QT_END_LICENSE$
59 **
60 ****************************************************************************/
61 
62 
63 #include "qgsauthcertificateinfo.h"
64 #include "qgsauthsslimportdialog.h"
65 #include "qgsauthsslconfigwidget.h"
66 #include "ui_qgsauthsslimporterrors.h"
67 
68 #include <QDir>
69 #include <QFileDialog>
70 #include <QFileInfo>
71 #include <QPushButton>
72 #include <QScrollBar>
73 #include <QStyle>
74 #include <QTimer>
75 #include <QToolButton>
76 #include <QSslCipher>
77 
78 #include "qgsauthguiutils.h"
79 #include "qgsauthmanager.h"
80 #include "qgslogger.h"
81 #include "qgsapplication.h"
82 
83 
85  : QDialog( parent )
86 {
87  if ( QgsApplication::authManager()->isDisabled() )
88  {
89  mAuthNotifyLayout = new QVBoxLayout;
90  this->setLayout( mAuthNotifyLayout );
91  mAuthNotify = new QLabel( QgsApplication::authManager()->disabledMessage(), this );
92  mAuthNotifyLayout->addWidget( mAuthNotify );
93  }
94  else
95  {
96  setupUi( this );
97  connect( btnCertPath, &QToolButton::clicked, this, &QgsAuthSslImportDialog::btnCertPath_clicked );
98  QStyle *style = QApplication::style();
99  lblWarningIcon->setPixmap( style->standardIcon( QStyle::SP_MessageBoxWarning ).pixmap( 48, 48 ) );
100  lblWarningIcon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
101 
102  closeButton()->setDefault( false );
103  saveButton()->setEnabled( false );
104 
105  leServer->setSelection( 0, leServer->text().size() );
106  pteSessionStatus->setReadOnly( true );
107  spinbxTimeout->setValue( 15 );
108 
109  grpbxServer->setCollapsed( false );
110  radioServerImport->setChecked( true );
111  frameServerImport->setEnabled( true );
112  radioFileImport->setChecked( false );
113  frameFileImport->setEnabled( false );
114 
115  connect( radioServerImport, &QAbstractButton::toggled,
116  this, &QgsAuthSslImportDialog::radioServerImportToggled );
117  connect( radioFileImport, &QAbstractButton::toggled,
118  this, &QgsAuthSslImportDialog::radioFileImportToggled );
119 
120  connect( leServer, &QLineEdit::textChanged,
121  this, &QgsAuthSslImportDialog::updateEnabledState );
122  connect( btnConnect, &QAbstractButton::clicked,
123  this, &QgsAuthSslImportDialog::secureConnect );
124  connect( leServer, &QLineEdit::returnPressed,
125  btnConnect, &QAbstractButton::click );
126 
127  connect( buttonBox, &QDialogButtonBox::accepted,
129  connect( buttonBox, &QDialogButtonBox::rejected,
130  this, &QDialog::reject );
131 
132  connect( wdgtSslConfig, &QgsAuthSslConfigWidget::readyToSaveChanged,
133  this, &QgsAuthSslImportDialog::widgetReadyToSaveChanged );
134  wdgtSslConfig->setEnabled( false );
135 
137  }
138 }
139 
141 {
142  wdgtSslConfig->saveSslCertConfig();
143  QDialog::accept();
144 }
145 
146 void QgsAuthSslImportDialog::updateEnabledState()
147 {
148  leServer->setStyleSheet( QString() );
149 
150  bool unconnected = !mSocket || mSocket->state() == QAbstractSocket::UnconnectedState;
151 
152  leServer->setReadOnly( !unconnected );
153  spinbxPort->setReadOnly( !unconnected );
154  spinbxTimeout->setReadOnly( !unconnected );
155 
156  leServer->setFocusPolicy( unconnected ? Qt::StrongFocus : Qt::NoFocus );
157  btnConnect->setEnabled( unconnected && !leServer->text().isEmpty() );
158 
159  bool connected = mSocket && mSocket->state() == QAbstractSocket::ConnectedState;
160  if ( connected && !mSocket->peerName().isEmpty() )
161  {
162  appendString( tr( "Connected to %1: %2" ).arg( mSocket->peerName() ).arg( mSocket->peerPort() ) );
163  }
164 }
165 
166 void QgsAuthSslImportDialog::secureConnect()
167 {
168  if ( leServer->text().isEmpty() )
169  {
170  return;
171  }
172 
173  leServer->setStyleSheet( QString() );
174  clearStatusCertificateConfig();
175 
176  if ( !mSocket )
177  {
178  mSocket = new QSslSocket( this );
179  connect( mSocket, &QAbstractSocket::stateChanged,
180  this, &QgsAuthSslImportDialog::socketStateChanged );
181  connect( mSocket, &QAbstractSocket::connected,
182  this, &QgsAuthSslImportDialog::socketConnected );
183  connect( mSocket, &QAbstractSocket::disconnected,
184  this, &QgsAuthSslImportDialog::socketDisconnected );
185  connect( mSocket, &QSslSocket::encrypted,
186  this, &QgsAuthSslImportDialog::socketEncrypted );
187  connect( mSocket, static_cast<void ( QAbstractSocket::* )( QAbstractSocket::SocketError )>( &QAbstractSocket::error ),
188  this, &QgsAuthSslImportDialog::socketError );
189  connect( mSocket, static_cast<void ( QSslSocket::* )( const QList<QSslError> & )>( &QSslSocket::sslErrors ),
190  this, &QgsAuthSslImportDialog::sslErrors );
191  connect( mSocket, &QIODevice::readyRead,
192  this, &QgsAuthSslImportDialog::socketReadyRead );
193  }
194 
195  QSslConfiguration sslConfig = mSocket->sslConfiguration();
196  sslConfig.setCaCertificates( mTrustedCAs );
197  mSocket->setSslConfiguration( sslConfig );
198 
199  if ( !mTimer )
200  {
201  mTimer = new QTimer( this );
202  connect( mTimer, &QTimer::timeout, this, &QgsAuthSslImportDialog::destroySocket );
203  }
204  mTimer->start( spinbxTimeout->value() * 1000 );
205 
206  mSocket->connectToHost( leServer->text(), spinbxPort->value() );
207  updateEnabledState();
208 }
209 
210 void QgsAuthSslImportDialog::socketStateChanged( QAbstractSocket::SocketState state )
211 {
212  if ( mExecErrorsDialog )
213  {
214  return;
215  }
216 
217  updateEnabledState();
218  if ( state == QAbstractSocket::UnconnectedState )
219  {
220  leServer->setFocus();
221  destroySocket();
222  }
223 }
224 
225 void QgsAuthSslImportDialog::socketConnected()
226 {
227  appendString( tr( "Socket CONNECTED" ) );
228  mSocket->startClientEncryption();
229 }
230 
231 void QgsAuthSslImportDialog::socketDisconnected()
232 {
233  appendString( tr( "Socket DISCONNECTED" ) );
234 }
235 
236 void QgsAuthSslImportDialog::socketEncrypted()
237 {
238  QgsDebugMsg( QStringLiteral( "socketEncrypted entered" ) );
239  if ( !mSocket )
240  return; // might have disconnected already
241 
242  appendString( tr( "Socket ENCRYPTED" ) );
243 
244  appendString( QStringLiteral( "%1: %2" ).arg( tr( "Protocol" ),
245  QgsAuthCertUtils::getSslProtocolName( mSocket->protocol() ) ) );
246 
247  QSslCipher ciph = mSocket->sessionCipher();
248  QString cipher = QStringLiteral( "%1: %2, %3 (%4/%5)" )
249  .arg( tr( "Session cipher" ), ciph.authenticationMethod(), ciph.name() )
250  .arg( ciph.usedBits() ).arg( ciph.supportedBits() );
251  appendString( cipher );
252 
253 
254 
255  wdgtSslConfig->setEnabled( true );
256  QString hostport( QStringLiteral( "%1:%2" ).arg( mSocket->peerName() ).arg( mSocket->peerPort() ) );
257  wdgtSslConfig->setSslCertificate( mSocket->peerCertificate(), hostport.trimmed() );
258  if ( !mSslErrors.isEmpty() )
259  {
260  wdgtSslConfig->appendSslIgnoreErrors( mSslErrors );
261  mSslErrors.clear();
262  }
263 
264 // checkCanSave();
265 
266  // must come after last state change, or gets reverted
267  leServer->setStyleSheet( QgsAuthGuiUtils::greenTextStyleSheet() );
268 
269  destroySocket();
270 }
271 
272 void QgsAuthSslImportDialog::socketError( QAbstractSocket::SocketError err )
273 {
274  Q_UNUSED( err )
275  if ( mSocket )
276  {
277  appendString( QStringLiteral( "%1: %2" ).arg( tr( "Socket ERROR" ), mSocket->errorString() ) );
278  }
279 }
280 
281 void QgsAuthSslImportDialog::socketReadyRead()
282 {
283  appendString( QString::fromUtf8( mSocket->readAll() ) );
284 }
285 
286 void QgsAuthSslImportDialog::destroySocket()
287 {
288  if ( !mSocket )
289  {
290  return;
291  }
292  if ( !mSocket->isEncrypted() )
293  {
294  appendString( tr( "Socket unavailable or not encrypted" ) );
295  }
296  mSocket->disconnectFromHost();
297  mSocket->deleteLater();
298  mSocket = nullptr;
299 }
300 
301 void QgsAuthSslImportDialog::sslErrors( const QList<QSslError> &errors )
302 {
303  if ( !mTimer->isActive() )
304  {
305  return;
306  }
307  mTimer->stop();
308 
309  QDialog errorDialog( this );
310  Ui_SslErrors ui;
311  ui.setupUi( &errorDialog );
312  const auto constErrors = errors;
313  for ( const QSslError &error : constErrors )
314  {
315  ui.sslErrorList->addItem( error.errorString() );
316  }
317 
318  mExecErrorsDialog = true;
319  if ( errorDialog.exec() == QDialog::Accepted )
320  {
321  mSocket->ignoreSslErrors();
322  mSslErrors = errors;
323  }
324  mExecErrorsDialog = false;
325 
326  mTimer->start();
327 
328  // did the socket state change?
329  if ( mSocket->state() != QAbstractSocket::ConnectedState )
330  socketStateChanged( mSocket->state() );
331 }
332 
333 void QgsAuthSslImportDialog::showCertificateInfo()
334 {
335  QList<QSslCertificate> peerchain( mSocket->peerCertificateChain() );
336 
337  if ( !peerchain.isEmpty() )
338  {
339  QSslCertificate cert = peerchain.takeFirst();
340  if ( !cert.isNull() )
341  {
342  QgsAuthCertInfoDialog *info = new QgsAuthCertInfoDialog( cert, false, this, peerchain );
343  info->exec();
344  info->deleteLater();
345  }
346  }
347 }
348 
349 void QgsAuthSslImportDialog::widgetReadyToSaveChanged( bool cansave )
350 {
351  saveButton()->setEnabled( cansave );
352 }
353 
354 void QgsAuthSslImportDialog::checkCanSave()
355 {
356  saveButton()->setEnabled( wdgtSslConfig->readyToSave() );
357  saveButton()->setDefault( false );
358  closeButton()->setDefault( false );
359 }
360 
361 void QgsAuthSslImportDialog::radioServerImportToggled( bool checked )
362 {
363  frameServerImport->setEnabled( checked );
364  clearStatusCertificateConfig();
365 }
366 
367 void QgsAuthSslImportDialog::radioFileImportToggled( bool checked )
368 {
369  frameFileImport->setEnabled( checked );
370  clearStatusCertificateConfig();
371 }
372 
373 void QgsAuthSslImportDialog::btnCertPath_clicked()
374 {
375  const QString &fn = getOpenFileName( tr( "Open Server Certificate File" ), tr( "All files (*.*);;PEM (*.pem);;DER (*.der)" ) );
376  if ( !fn.isEmpty() )
377  {
378  leCertPath->setText( fn );
379  loadCertFromFile();
380  }
381 }
382 
383 void QgsAuthSslImportDialog::clearCertificateConfig()
384 {
385  wdgtSslConfig->resetSslCertConfig();
386  wdgtSslConfig->setEnabled( false );
387 }
388 
389 void QgsAuthSslImportDialog::clearStatusCertificateConfig()
390 {
391  mSslErrors.clear();
392  pteSessionStatus->clear();
393  saveButton()->setEnabled( false );
394  clearCertificateConfig();
395 }
396 
397 void QgsAuthSslImportDialog::loadCertFromFile()
398 {
399  clearStatusCertificateConfig();
400  QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromFile( leCertPath->text() ) );
401 
402  if ( certs.isEmpty() )
403  {
404  appendString( tr( "Could not load any certs from file" ) );
405  return;
406  }
407 
408  QSslCertificate cert( certs.first() );
409  if ( cert.isNull() )
410  {
411  appendString( tr( "Could not load server cert from file" ) );
412  return;
413  }
414 
416  {
417  appendString( tr( "Certificate does not appear for be for an SSL server. "
418  "You can still add a configuration, if you know it is the correct certificate." ) );
419  }
420 
421  wdgtSslConfig->setEnabled( true );
422  wdgtSslConfig->setSslHost( QString() );
423  wdgtSslConfig->setSslCertificate( cert );
424  if ( !mSslErrors.isEmpty() )
425  {
426  wdgtSslConfig->appendSslIgnoreErrors( mSslErrors );
427  mSslErrors.clear();
428  }
429 // checkCanSave();
430 }
431 
432 void QgsAuthSslImportDialog::appendString( const QString &line )
433 {
434  QTextCursor cursor( pteSessionStatus->textCursor() );
435  cursor.movePosition( QTextCursor::End );
436  cursor.insertText( line + '\n' );
437 // pteSessionStatus->verticalScrollBar()->setValue( pteSessionStatus->verticalScrollBar()->maximum() );
438 }
439 
440 QPushButton *QgsAuthSslImportDialog::saveButton()
441 {
442  return buttonBox->button( QDialogButtonBox::Save );
443 }
444 
445 QPushButton *QgsAuthSslImportDialog::closeButton()
446 {
447  return buttonBox->button( QDialogButtonBox::Close );
448 }
449 
450 QString QgsAuthSslImportDialog::getOpenFileName( const QString &title, const QString &extfilter )
451 {
452  QgsSettings settings;
453  QString recentdir = settings.value( QStringLiteral( "UI/lastAuthImportSslOpenFileDir" ), QDir::homePath() ).toString();
454  QString f = QFileDialog::getOpenFileName( this, title, recentdir, extfilter );
455 
456  // return dialog focus on Mac
457  this->raise();
458  this->activateWindow();
459 
460  if ( !f.isEmpty() )
461  {
462  settings.setValue( QStringLiteral( "UI/lastAuthImportSslOpenFileDir" ), QFileInfo( f ).absoluteDir().path() );
463  }
464  return f;
465 }
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:174
qgsauthmanager.h
QgsAuthGuiUtils::greenTextStyleSheet
static QString greenTextStyleSheet(const QString &selector="*")
Green text stylesheet representing valid, trusted, etc. certificate.
Definition: qgsauthguiutils.cpp:51
QgsAuthCertUtils::certificateIsSslServer
static bool certificateIsSslServer(const QSslCertificate &cert)
Gets whether a certificate is probably used for a SSL server.
Definition: qgsauthcertutils.cpp:1027
QgsSettings
Definition: qgssettings.h:61
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsAuthCertUtils::getSslProtocolName
static QString getSslProtocolName(QSsl::SslProtocol protocol)
SSL Protocol name strings per enum.
Definition: qgsauthcertutils.cpp:36
QgsAuthSslImportDialog::accept
void accept() override
Definition: qgsauthsslimportdialog.cpp:140
qgsauthguiutils.h
QgsApplication::authManager
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
Definition: qgsapplication.cpp:1263
qgsapplication.h
QgsAuthSslConfigWidget::readyToSaveChanged
void readyToSaveChanged(bool cansave)
Emitted when the configuration can be saved changes.
QgsAuthSslImportDialog::QgsAuthSslImportDialog
QgsAuthSslImportDialog(QWidget *parent=nullptr)
Construct dialog for importing certificates.
Definition: qgsauthsslimportdialog.cpp:84
QgsAuthCertInfoDialog
Definition: qgsauthcertificateinfo.h:145
QgsSettings::setValue
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Definition: qgssettings.cpp:289
QgsAuthManager::trustedCaCertsCache
const QList< QSslCertificate > trustedCaCertsCache()
trustedCaCertsCache cache of trusted certificate authorities, ready for network connections
Definition: qgsauthmanager.h:622
qgsauthcertificateinfo.h
QgsAuthCertUtils::certsFromFile
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Returns a list of concatenated certs from a PEM or DER formatted file.
Definition: qgsauthcertutils.cpp:125
qgsauthsslconfigwidget.h
qgslogger.h
qgsauthsslimportdialog.h