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