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