QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
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
65#include "moc_qgsauthsslimportdialog.cpp"
67#include "ui_qgsauthsslimporterrors.h"
68
69#include <QDir>
70#include <QFileDialog>
71#include <QFileInfo>
72#include <QPushButton>
73#include <QScrollBar>
74#include <QStyle>
75#include <QTimer>
76#include <QToolButton>
77#include <QSslCipher>
78
79#include "qgsauthguiutils.h"
80#include "qgsauthmanager.h"
81#include "qgslogger.h"
82#include "qgsapplication.h"
83
84
86 : QDialog( parent )
87{
88 if ( QgsApplication::authManager()->isDisabled() )
89 {
90 mAuthNotifyLayout = new QVBoxLayout;
91 this->setLayout( mAuthNotifyLayout );
92 mAuthNotify = new QLabel( QgsApplication::authManager()->disabledMessage(), this );
93 mAuthNotifyLayout->addWidget( mAuthNotify );
94 }
95 else
96 {
97 setupUi( this );
98 connect( btnCertPath, &QToolButton::clicked, this, &QgsAuthSslImportDialog::btnCertPath_clicked );
99 QStyle *style = QApplication::style();
100 lblWarningIcon->setPixmap( style->standardIcon( QStyle::SP_MessageBoxWarning ).pixmap( 48, 48 ) );
101 lblWarningIcon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
102
103 closeButton()->setDefault( false );
104 saveButton()->setEnabled( false );
105
106 leServer->setSelection( 0, leServer->text().size() );
107 pteSessionStatus->setReadOnly( true );
108 spinbxTimeout->setClearValue( 15 );
109 spinbxTimeout->setValue( 15 );
110 spinbxPort->setClearValue( 443 );
111
112 grpbxServer->setCollapsed( false );
113 radioServerImport->setChecked( true );
114 frameServerImport->setEnabled( true );
115 radioFileImport->setChecked( false );
116 frameFileImport->setEnabled( false );
117
118 connect( radioServerImport, &QAbstractButton::toggled,
119 this, &QgsAuthSslImportDialog::radioServerImportToggled );
120 connect( radioFileImport, &QAbstractButton::toggled,
121 this, &QgsAuthSslImportDialog::radioFileImportToggled );
122
123 connect( leServer, &QLineEdit::textChanged,
124 this, &QgsAuthSslImportDialog::updateEnabledState );
125 connect( btnConnect, &QAbstractButton::clicked,
126 this, &QgsAuthSslImportDialog::secureConnect );
127 connect( leServer, &QLineEdit::returnPressed,
128 btnConnect, &QAbstractButton::click );
129
130 connect( buttonBox, &QDialogButtonBox::accepted,
132 connect( buttonBox, &QDialogButtonBox::rejected,
133 this, &QDialog::reject );
134
135 connect( wdgtSslConfig, &QgsAuthSslConfigWidget::readyToSaveChanged,
136 this, &QgsAuthSslImportDialog::widgetReadyToSaveChanged );
137 wdgtSslConfig->setEnabled( false );
138
140 }
141}
142
144{
145 wdgtSslConfig->saveSslCertConfig();
146 QDialog::accept();
147}
148
149void QgsAuthSslImportDialog::updateEnabledState()
150{
151 leServer->setStyleSheet( QString() );
152
153 const bool unconnected = !mSocket || mSocket->state() == QAbstractSocket::UnconnectedState;
154
155 leServer->setReadOnly( !unconnected );
156 spinbxPort->setReadOnly( !unconnected );
157 spinbxTimeout->setReadOnly( !unconnected );
158
159 leServer->setFocusPolicy( unconnected ? Qt::StrongFocus : Qt::NoFocus );
160 btnConnect->setEnabled( unconnected && !leServer->text().isEmpty() );
161
162 const bool connected = mSocket && mSocket->state() == QAbstractSocket::ConnectedState;
163 if ( connected && !mSocket->peerName().isEmpty() )
164 {
165 appendString( tr( "Connected to %1: %2" ).arg( mSocket->peerName() ).arg( mSocket->peerPort() ) );
166 }
167}
168
169void QgsAuthSslImportDialog::secureConnect()
170{
171 if ( leServer->text().isEmpty() )
172 {
173 return;
174 }
175
176 leServer->setStyleSheet( QString() );
177 clearStatusCertificateConfig();
178
179 if ( !mSocket )
180 {
181 mSocket = new QSslSocket( this );
182 connect( mSocket, &QAbstractSocket::stateChanged,
183 this, &QgsAuthSslImportDialog::socketStateChanged );
184 connect( mSocket, &QAbstractSocket::connected,
185 this, &QgsAuthSslImportDialog::socketConnected );
186 connect( mSocket, &QAbstractSocket::disconnected,
187 this, &QgsAuthSslImportDialog::socketDisconnected );
188 connect( mSocket, &QSslSocket::encrypted,
189 this, &QgsAuthSslImportDialog::socketEncrypted );
190#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
191 connect( mSocket, static_cast<void ( QAbstractSocket::* )( QAbstractSocket::SocketError )>( &QAbstractSocket::error ),
192 this, &QgsAuthSslImportDialog::socketError );
193#else
194 connect( mSocket, &QAbstractSocket::errorOccurred, this, &QgsAuthSslImportDialog::socketError );
195#endif
196 connect( mSocket, static_cast<void ( QSslSocket::* )( const QList<QSslError> & )>( &QSslSocket::sslErrors ),
197 this, &QgsAuthSslImportDialog::sslErrors );
198 connect( mSocket, &QIODevice::readyRead,
199 this, &QgsAuthSslImportDialog::socketReadyRead );
200 }
201
202 QSslConfiguration sslConfig = mSocket->sslConfiguration();
203 sslConfig.setCaCertificates( mTrustedCAs );
204 mSocket->setSslConfiguration( sslConfig );
205
206 if ( !mTimer )
207 {
208 mTimer = new QTimer( this );
209 connect( mTimer, &QTimer::timeout, this, &QgsAuthSslImportDialog::destroySocket );
210 }
211 mTimer->start( spinbxTimeout->value() * 1000 );
212
213 mSocket->connectToHost( leServer->text(), spinbxPort->value() );
214 updateEnabledState();
215}
216
217void 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
232void QgsAuthSslImportDialog::socketConnected()
233{
234 appendString( tr( "Socket CONNECTED" ) );
235 mSocket->startClientEncryption();
236}
237
238void QgsAuthSslImportDialog::socketDisconnected()
239{
240 appendString( tr( "Socket DISCONNECTED" ) );
241}
242
243void QgsAuthSslImportDialog::socketEncrypted()
244{
245 QgsDebugMsgLevel( QStringLiteral( "socketEncrypted entered" ), 2 );
246 if ( !mSocket )
247 return; // might have disconnected already
248
249 appendString( tr( "Socket ENCRYPTED" ) );
250
251 appendString( QStringLiteral( "%1: %2" ).arg( tr( "Protocol" ),
252 QgsAuthCertUtils::getSslProtocolName( mSocket->protocol() ) ) );
253
254 const QSslCipher ciph = mSocket->sessionCipher();
255 const QString cipher = QStringLiteral( "%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 const QString hostport( QStringLiteral( "%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
279void QgsAuthSslImportDialog::socketError( QAbstractSocket::SocketError err )
280{
281 Q_UNUSED( err )
282 if ( mSocket )
283 {
284 appendString( QStringLiteral( "%1: %2" ).arg( tr( "Socket ERROR" ), mSocket->errorString() ) );
285 }
286}
287
288void QgsAuthSslImportDialog::socketReadyRead()
289{
290 appendString( QString::fromUtf8( mSocket->readAll() ) );
291}
292
293void 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
308void 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 const auto constErrors = errors;
320 for ( const QSslError &error : constErrors )
321 {
322 ui.sslErrorList->addItem( error.errorString() );
323 }
324
325 mExecErrorsDialog = true;
326 if ( errorDialog.exec() == QDialog::Accepted )
327 {
328 mSocket->ignoreSslErrors();
329 mSslErrors = errors;
330 }
331 mExecErrorsDialog = false;
332
333 mTimer->start();
334
335 // did the socket state change?
336 if ( mSocket->state() != QAbstractSocket::ConnectedState )
337 socketStateChanged( mSocket->state() );
338}
339
340void QgsAuthSslImportDialog::showCertificateInfo()
341{
342 QList<QSslCertificate> peerchain( mSocket->peerCertificateChain() );
343
344 if ( !peerchain.isEmpty() )
345 {
346 const QSslCertificate cert = peerchain.takeFirst();
347 if ( !cert.isNull() )
348 {
349 QgsAuthCertInfoDialog *info = new QgsAuthCertInfoDialog( cert, false, this, peerchain );
350 info->exec();
351 info->deleteLater();
352 }
353 }
354}
355
356void QgsAuthSslImportDialog::widgetReadyToSaveChanged( bool cansave )
357{
358 saveButton()->setEnabled( cansave );
359}
360
361void QgsAuthSslImportDialog::checkCanSave()
362{
363 saveButton()->setEnabled( wdgtSslConfig->readyToSave() );
364 saveButton()->setDefault( false );
365 closeButton()->setDefault( false );
366}
367
368void QgsAuthSslImportDialog::radioServerImportToggled( bool checked )
369{
370 frameServerImport->setEnabled( checked );
371 clearStatusCertificateConfig();
372}
373
374void QgsAuthSslImportDialog::radioFileImportToggled( bool checked )
375{
376 frameFileImport->setEnabled( checked );
377 clearStatusCertificateConfig();
378}
379
380void QgsAuthSslImportDialog::btnCertPath_clicked()
381{
382 const QString &fn = getOpenFileName( tr( "Open Server Certificate File" ), tr( "All files (*.*);;PEM (*.pem);;DER (*.der)" ) );
383 if ( !fn.isEmpty() )
384 {
385 leCertPath->setText( fn );
386 loadCertFromFile();
387 }
388}
389
390void QgsAuthSslImportDialog::clearCertificateConfig()
391{
392 wdgtSslConfig->resetSslCertConfig();
393 wdgtSslConfig->setEnabled( false );
394}
395
396void QgsAuthSslImportDialog::clearStatusCertificateConfig()
397{
398 mSslErrors.clear();
399 pteSessionStatus->clear();
400 saveButton()->setEnabled( false );
401 clearCertificateConfig();
402}
403
404void QgsAuthSslImportDialog::loadCertFromFile()
405{
406 clearStatusCertificateConfig();
407 QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromFile( leCertPath->text() ) );
408
409 if ( certs.isEmpty() )
410 {
411 appendString( tr( "Could not load any certs from file" ) );
412 return;
413 }
414
415 const QSslCertificate cert( certs.first() );
416 if ( cert.isNull() )
417 {
418 appendString( tr( "Could not load server cert from file" ) );
419 return;
420 }
421
423 {
424 appendString( tr( "Certificate does not appear for be for an SSL server. "
425 "You can still add a configuration, if you know it is the correct certificate." ) );
426 }
427
428 wdgtSslConfig->setEnabled( true );
429 wdgtSslConfig->setSslHost( QString() );
430 wdgtSslConfig->setSslCertificate( cert );
431 if ( !mSslErrors.isEmpty() )
432 {
433 wdgtSslConfig->appendSslIgnoreErrors( mSslErrors );
434 mSslErrors.clear();
435 }
436// checkCanSave();
437}
438
439void QgsAuthSslImportDialog::appendString( const QString &line )
440{
441 QTextCursor cursor( pteSessionStatus->textCursor() );
442 cursor.movePosition( QTextCursor::End );
443 cursor.insertText( line + '\n' );
444// pteSessionStatus->verticalScrollBar()->setValue( pteSessionStatus->verticalScrollBar()->maximum() );
445}
446
447QPushButton *QgsAuthSslImportDialog::saveButton()
448{
449 return buttonBox->button( QDialogButtonBox::Save );
450}
451
452QPushButton *QgsAuthSslImportDialog::closeButton()
453{
454 return buttonBox->button( QDialogButtonBox::Close );
455}
456
457QString QgsAuthSslImportDialog::getOpenFileName( const QString &title, const QString &extfilter )
458{
459 QgsSettings settings;
460 const QString recentdir = settings.value( QStringLiteral( "UI/lastAuthImportSslOpenFileDir" ), QDir::homePath() ).toString();
461 QString f = QFileDialog::getOpenFileName( this, title, recentdir, extfilter );
462
463 // return dialog focus on Mac
464 this->raise();
465 this->activateWindow();
466
467 if ( !f.isEmpty() )
468 {
469 settings.setValue( QStringLiteral( "UI/lastAuthImportSslOpenFileDir" ), QFileInfo( f ).absoluteDir().path() );
470 }
471 return f;
472}
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