QGIS API Documentation  2.14.0-Essen
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 
82 
84  : QDialog( parent )
85  , mSocket( nullptr )
86  , mExecErrorsDialog( false )
87  , mTimer( nullptr )
88  , mSslErrors( QList<QSslError>() )
89  , mTrustedCAs( QList<QSslCertificate>() )
90  , mAuthNotifyLayout( nullptr )
91  , mAuthNotify( nullptr )
92 {
93  if ( QgsAuthManager::instance()->isDisabled() )
94  {
95  mAuthNotifyLayout = new QVBoxLayout;
96  this->setLayout( mAuthNotifyLayout );
97  mAuthNotify = new QLabel( QgsAuthManager::instance()->disabledMessage(), this );
98  mAuthNotifyLayout->addWidget( mAuthNotify );
99  }
100  else
101  {
102  setupUi( this );
104  lblWarningIcon->setPixmap( style->standardIcon( QStyle::SP_MessageBoxWarning ).pixmap( 48, 48 ) );
105  lblWarningIcon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
106 
107  closeButton()->setDefault( false );
108  saveButton()->setEnabled( false );
109 
110  leServer->setSelection( 0, leServer->text().size() );
111  pteSessionStatus->setReadOnly( true );
112  spinbxTimeout->setValue( 15 );
113 
114  grpbxServer->setCollapsed( false );
115  radioServerImport->setChecked( true );
116  frameServerImport->setEnabled( true );
117  radioFileImport->setChecked( false );
118  frameFileImport->setEnabled( false );
119 
120  connect( radioServerImport, SIGNAL( toggled( bool ) ),
121  this, SLOT( radioServerImportToggled( bool ) ) );
122  connect( radioFileImport, SIGNAL( toggled( bool ) ),
123  this, SLOT( radioFileImportToggled( bool ) ) );
124 
125  connect( leServer, SIGNAL( textChanged( QString ) ),
126  this, SLOT( updateEnabledState() ) );
127  connect( btnConnect, SIGNAL( clicked() ),
128  this, SLOT( secureConnect() ) );
129  connect( leServer, SIGNAL( returnPressed() ),
130  btnConnect, SLOT( click() ) );
131 
132  connect( buttonBox, SIGNAL( accepted() ),
133  this, SLOT( accept() ) );
134  connect( buttonBox, SIGNAL( rejected() ),
135  this, SLOT( reject() ) );
136 
137  connect( wdgtSslConfig, SIGNAL( readyToSaveChanged( bool ) ),
138  this, SLOT( widgetReadyToSaveChanged( bool ) ) );
139  wdgtSslConfig->setEnabled( false );
140 
142  }
143 }
144 
146 {
147 }
148 
150 {
151  wdgtSslConfig->saveSslCertConfig();
152  QDialog::accept();
153 }
154 
155 void QgsAuthSslImportDialog::updateEnabledState()
156 {
157  leServer->setStyleSheet( "" );
158 
159  bool unconnected = !mSocket || mSocket->state() == QAbstractSocket::UnconnectedState;
160 
161  leServer->setReadOnly( !unconnected );
162  spinbxPort->setReadOnly( !unconnected );
163  spinbxTimeout->setReadOnly( !unconnected );
164 
165  leServer->setFocusPolicy( unconnected ? Qt::StrongFocus : Qt::NoFocus );
166  btnConnect->setEnabled( unconnected && !leServer->text().isEmpty() );
167 
168  bool connected = mSocket && mSocket->state() == QAbstractSocket::ConnectedState;
169  if ( connected && !mSocket->peerName().isEmpty() )
170  {
171  appendString( tr( "Connected to %1:%2" ).arg( mSocket->peerName() ).arg( mSocket->peerPort() ) );
172  }
173 }
174 
175 void QgsAuthSslImportDialog::secureConnect()
176 {
177  if ( leServer->text().isEmpty() )
178  {
179  return;
180  }
181 
182  leServer->setStyleSheet( "" );
183  clearStatusCertificateConfig();
184 
185  if ( !mSocket )
186  {
187  mSocket = new QSslSocket( this );
188  connect( mSocket, SIGNAL( stateChanged( QAbstractSocket::SocketState ) ),
189  this, SLOT( socketStateChanged( QAbstractSocket::SocketState ) ) );
190  connect( mSocket, SIGNAL( connected() ),
191  this, SLOT( socketConnected() ) );
192  connect( mSocket, SIGNAL( disconnected() ),
193  this, SLOT( socketDisconnected() ) );
194  connect( mSocket, SIGNAL( encrypted() ),
195  this, SLOT( socketEncrypted() ) );
196  connect( mSocket, SIGNAL( error( QAbstractSocket::SocketError ) ),
197  this, SLOT( socketError( QAbstractSocket::SocketError ) ) );
198  connect( mSocket, SIGNAL( sslErrors( QList<QSslError> ) ),
199  this, SLOT( sslErrors( QList<QSslError> ) ) );
200  connect( mSocket, SIGNAL( readyRead() ),
201  this, SLOT( socketReadyRead() ) );
202  }
203 
204  mSocket->setCaCertificates( mTrustedCAs );
205 
206  if ( !mTimer )
207  {
208  mTimer = new QTimer( this );
209  connect( mTimer, SIGNAL( timeout() ), this, SLOT( destroySocket() ) );
210  }
211  mTimer->start( spinbxTimeout->value() * 1000 );
212 
213  mSocket->connectToHost( leServer->text(), spinbxPort->value() );
214  updateEnabledState();
215 }
216 
217 void QgsAuthSslImportDialog::socketStateChanged( QAbstractSocket::SocketState state )
218 {
219  if ( mExecErrorsDialog )
220  {
221  return;
222  }
223 
224  updateEnabledState();
225  if ( state == QAbstractSocket::UnconnectedState )
226  {
227  leServer->setFocus();
228  destroySocket();
229  }
230 }
231 
232 void QgsAuthSslImportDialog::socketConnected()
233 {
234  appendString( tr( "Socket CONNECTED" ) );
235  mSocket->startClientEncryption();
236 }
237 
238 void QgsAuthSslImportDialog::socketDisconnected()
239 {
240  appendString( tr( "Socket DISCONNECTED" ) );
241 }
242 
243 void QgsAuthSslImportDialog::socketEncrypted()
244 {
245  QgsDebugMsg( "socketEncrypted entered" );
246  if ( !mSocket )
247  return; // might have disconnected already
248 
249  appendString( tr( "Socket ENCRYPTED" ) );
250 
251  appendString( QString( "%1: %2" ).arg( tr( "Protocol" ),
253 
254  QSslCipher ciph = mSocket->sessionCipher();
255  QString cipher = QString( "%1: %2, %3 (%4/%5)" )
256  .arg( tr( "Session cipher" ), ciph.authenticationMethod(), ciph.name() )
257  .arg( ciph.usedBits() ).arg( ciph.supportedBits() );
258  appendString( cipher );
259 
260 
261 
262  wdgtSslConfig->setEnabled( true );
263  QString hostport( QString( "%1:%2" ).arg( mSocket->peerName() ).arg( mSocket->peerPort() ) );
264  wdgtSslConfig->setSslCertificate( mSocket->peerCertificate(), hostport.trimmed() );
265  if ( !mSslErrors.isEmpty() )
266  {
267  wdgtSslConfig->appendSslIgnoreErrors( mSslErrors );
268  mSslErrors.clear();
269  }
270 
271 // checkCanSave();
272 
273  // must come after last state change, or gets reverted
274  leServer->setStyleSheet( QgsAuthGuiUtils::greenTextStyleSheet() );
275 
276  destroySocket();
277 }
278 
279 void QgsAuthSslImportDialog::socketError( QAbstractSocket::SocketError err )
280 {
281  Q_UNUSED( err );
282  if ( mSocket )
283  {
284  appendString( QString( "%1: %2" ).arg( tr( "Socket ERROR" ), mSocket->errorString() ) );
285  }
286 }
287 
288 void QgsAuthSslImportDialog::socketReadyRead()
289 {
290  appendString( QString::fromUtf8( mSocket->readAll() ) );
291 }
292 
293 void QgsAuthSslImportDialog::destroySocket()
294 {
295  if ( !mSocket )
296  {
297  return;
298  }
299  if ( !mSocket->isEncrypted() )
300  {
301  appendString( tr( "Socket unavailable or not encrypted" ) );
302  }
303  mSocket->disconnectFromHost();
304  mSocket->deleteLater();
305  mSocket = nullptr;
306 }
307 
308 void QgsAuthSslImportDialog::sslErrors( const QList<QSslError> &errors )
309 {
310  if ( !mTimer->isActive() )
311  {
312  return;
313  }
314  mTimer->stop();
315 
316  QDialog errorDialog( this );
317  Ui_SslErrors ui;
318  ui.setupUi( &errorDialog );
319  connect( ui.certificateChainButton, SIGNAL( clicked() ),
320  this, SLOT( showCertificateInfo() ) );
321 
322  Q_FOREACH ( const QSslError &error, errors )
323  {
324  ui.sslErrorList->addItem( error.errorString() );
325  }
326 
327  mExecErrorsDialog = true;
328  if ( errorDialog.exec() == QDialog::Accepted )
329  {
330  mSocket->ignoreSslErrors();
331  mSslErrors = errors;
332  }
333  mExecErrorsDialog = false;
334 
335  mTimer->start();
336 
337  // did the socket state change?
338  if ( mSocket->state() != QAbstractSocket::ConnectedState )
339  socketStateChanged( mSocket->state() );
340 }
341 
342 void QgsAuthSslImportDialog::showCertificateInfo()
343 {
344  QList<QSslCertificate> peerchain( mSocket->peerCertificateChain() );
345 
346  if ( !peerchain.isEmpty() )
347  {
348  QSslCertificate cert = peerchain.takeFirst();
349  if ( !cert.isNull() )
350  {
351  QgsAuthCertInfoDialog *info = new QgsAuthCertInfoDialog( cert, false, this, peerchain );
352  info->exec();
353  info->deleteLater();
354  }
355  }
356 }
357 
358 void QgsAuthSslImportDialog::widgetReadyToSaveChanged( bool cansave )
359 {
360  saveButton()->setEnabled( cansave );
361 }
362 
363 void QgsAuthSslImportDialog::checkCanSave()
364 {
365  saveButton()->setEnabled( wdgtSslConfig->readyToSave() );
366  saveButton()->setDefault( false );
367  closeButton()->setDefault( false );
368 }
369 
370 void QgsAuthSslImportDialog::radioServerImportToggled( bool checked )
371 {
372  frameServerImport->setEnabled( checked );
373  clearStatusCertificateConfig();
374 }
375 
376 void QgsAuthSslImportDialog::radioFileImportToggled( bool checked )
377 {
378  frameFileImport->setEnabled( checked );
379  clearStatusCertificateConfig();
380 }
381 
382 void QgsAuthSslImportDialog::on_btnCertPath_clicked()
383 {
384  const QString& fn = getOpenFileName( tr( "Open Server Certificate File" ), tr( "PEM (*.pem);;DER (*.der)" ) );
385  if ( !fn.isEmpty() )
386  {
387  leCertPath->setText( fn );
388  loadCertFromFile();
389  }
390 }
391 
392 void QgsAuthSslImportDialog::clearCertificateConfig()
393 {
394  wdgtSslConfig->resetSslCertConfig();
395  wdgtSslConfig->setEnabled( false );
396 }
397 
398 void QgsAuthSslImportDialog::clearStatusCertificateConfig()
399 {
400  mSslErrors.clear();
401  pteSessionStatus->clear();
402  saveButton()->setEnabled( false );
403  clearCertificateConfig();
404 }
405 
406 void QgsAuthSslImportDialog::loadCertFromFile()
407 {
408  clearStatusCertificateConfig();
409  QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromFile( leCertPath->text() ) );
410 
411  if ( certs.isEmpty() )
412  {
413  appendString( tr( "Could not load any certs from file" ) );
414  return;
415  }
416 
417  QSslCertificate cert( certs.first() );
418  if ( cert.isNull() )
419  {
420  appendString( tr( "Could not load server cert from file" ) );
421  return;
422  }
423 
425  {
426  appendString( tr( "Certificate does not appear for be for an SSL server. "
427  "You can still add a configuration, if you know it is the correct certificate." ) );
428  }
429 
430  wdgtSslConfig->setEnabled( true );
431  wdgtSslConfig->setSslHost( "" );
432  wdgtSslConfig->setSslCertificate( cert );
433  if ( !mSslErrors.isEmpty() )
434  {
435  wdgtSslConfig->appendSslIgnoreErrors( mSslErrors );
436  mSslErrors.clear();
437  }
438 // checkCanSave();
439 }
440 
441 void QgsAuthSslImportDialog::appendString( const QString &line )
442 {
443  QTextCursor cursor( pteSessionStatus->textCursor() );
444  cursor.movePosition( QTextCursor::End );
445  cursor.insertText( line + '\n' );
446 // pteSessionStatus->verticalScrollBar()->setValue( pteSessionStatus->verticalScrollBar()->maximum() );
447 }
448 
449 QPushButton *QgsAuthSslImportDialog::saveButton()
450 {
451  return buttonBox->button( QDialogButtonBox::Save );
452 }
453 
454 QPushButton *QgsAuthSslImportDialog::closeButton()
455 {
456  return buttonBox->button( QDialogButtonBox::Close );
457 }
458 
459 QString QgsAuthSslImportDialog::getOpenFileName( const QString &title, const QString &extfilter )
460 {
461  QSettings settings;
462  QString recentdir = settings.value( "UI/lastAuthImportSslOpenFileDir", QDir::homePath() ).toString();
463  QString f = QFileDialog::getOpenFileName( this, title, recentdir, extfilter );
464 
465  // return dialog focus on Mac
466  this->raise();
467  this->activateWindow();
468 
469  if ( !f.isEmpty() )
470  {
471  settings.setValue( "UI/lastAuthImportSslOpenFileDir", QFileInfo( f ).absoluteDir().path() );
472  }
473  return f;
474 }
QSslCertificate peerCertificate() const
void clear()
void ignoreSslErrors(const QList< QSslError > &errors)
void setupUi(QWidget *widget)
virtual void reject()
QCursor cursor() const
static QgsAuthManager * instance()
Enforce singleton pattern.
SocketState state() const
bool isNull() const
QString errorString() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
void rejected()
QStyle * style() const
void startClientEncryption()
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Return list of concatenated certs from a PEM or DER formatted file.
void accepted()
int exec()
QString homePath()
QString tr(const char *sourceText, const char *disambiguation, int n)
QPixmap pixmap(const QSize &size, Mode mode, State state) const
void connectToHost(const QString &hostName, quint16 port, QFlags< QIODevice::OpenModeFlag > openMode)
void setValue(const QString &key, const QVariant &value)
bool isEncrypted() const
void setEnabled(bool)
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
QString fromUtf8(const char *str, int size)
Dialog wrapper for widget displaying detailed info on a certificate and its hierarchical trust chain...
QgsAuthSslImportDialog(QWidget *parent=nullptr)
Construct dialog for importing certificates.
void setLayout(QLayout *layout)
static QString greenTextStyleSheet(const QString &selector="*")
Green text stylesheet representing valid, trusted, etc.
bool isEmpty() const
bool isEmpty() const
QString trimmed() const
const QList< QSslCertificate > getTrustedCaCertsCache()
Get cache of trusted certificate authorities, ready for network connections.
QByteArray readAll()
QSslCipher sessionCipher() const
QList< QSslCertificate > peerCertificateChain() const
void deleteLater()
T & first()
virtual void accept()
static bool certificateIsSslServer(const QSslCertificate &cert)
Get whether a certificate is probably used for a SSL server.
QString errorString() const
QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const
void disconnectFromHost()
static QString getSslProtocolName(QSsl::SslProtocol protocol)
SSL Protocol name strings per enum.
void stop()
QVariant value(const QString &key, const QVariant &defaultValue) const
QSsl::SslProtocol protocol() const
void activateWindow()
QStyle * style()
QString peerName() const
void start(int msec)
quint16 peerPort() const
void setCaCertificates(const QList< QSslCertificate > &certificates)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
bool isActive() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void accept() override
Overridden slot of base dialog.
void setDefault(bool)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const