1 /***************************************************************************
2     qgsauthconfigselect.cpp
3     ---------------------
4     begin                : October 5, 2014
5     copyright            : (C) 2014 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 #include "qgsauthconfigselect.h"
18 #include "ui_qgsauthconfigselect.h"
19 
20 #include "qgsauthconfig.h"
21 #include "qgsauthguiutils.h"
22 #include "qgsauthmanager.h"
23 #include "qgsauthconfigedit.h"
24 #include "qgslogger.h"
25 #include "qgsapplication.h"
26 #include "qgsauthmethodmetadata.h"
27 
28 #include <QHash>
29 #include <QMessageBox>
30 #include <QTimer>
31 #include <QRegularExpression>
32 
33 
QgsAuthConfigSelect(QWidget * parent,const QString & dataprovider)34 QgsAuthConfigSelect::QgsAuthConfigSelect( QWidget *parent, const QString &dataprovider )
35   : QWidget( parent )
36   , mDataProvider( dataprovider )
37 {
38   if ( QgsApplication::authManager()->isDisabled() )
39   {
40     mDisabled = true;
41     mAuthNotifyLayout = new QVBoxLayout;
42     this->setLayout( mAuthNotifyLayout );
43     mAuthNotify = new QLabel( QgsApplication::authManager()->disabledMessage(), this );
44     mAuthNotifyLayout->addWidget( mAuthNotify );
45   }
46   else
47   {
48     setupUi( this );
49     connect( cmbConfigSelect, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsAuthConfigSelect::cmbConfigSelect_currentIndexChanged );
50     connect( btnConfigAdd, &QToolButton::clicked, this, &QgsAuthConfigSelect::btnConfigAdd_clicked );
51     connect( btnConfigEdit, &QToolButton::clicked, this, &QgsAuthConfigSelect::btnConfigEdit_clicked );
52     connect( btnConfigRemove, &QToolButton::clicked, this, &QgsAuthConfigSelect::btnConfigRemove_clicked );
53     connect( btnConfigMsgClear, &QToolButton::clicked, this, &QgsAuthConfigSelect::btnConfigMsgClear_clicked );
54 
55     // Set icons and remove texts
56     btnConfigAdd->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
57     btnConfigRemove->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyRemove.svg" ) ) );
58     btnConfigEdit->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
59     btnConfigMsgClear->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconClose.svg" ) ) );
60 
61     btnConfigAdd->setText( QString() );
62     btnConfigRemove->setText( QString() );
63     btnConfigEdit->setText( QString() );
64     btnConfigMsgClear->setText( QString() );
65 
66     leConfigMsg->setStyleSheet( QStringLiteral( "QLineEdit{background-color: %1}" )
67                                 .arg( QgsAuthGuiUtils::yellowColor().name() ) );
68 
69     clearConfig();
70     clearMessage();
71     populateConfigSelector();
72   }
73 }
74 
setConfigId(const QString & authcfg)75 void QgsAuthConfigSelect::setConfigId( const QString &authcfg )
76 {
77   if ( mDisabled && mAuthNotify )
78   {
79     mAuthNotify->setText( QgsApplication::authManager()->disabledMessage() + "\n\n" +
80                           tr( "Authentication config id not loaded: %1" ).arg( authcfg ) );
81   }
82   else
83   {
84     if ( mAuthCfg != authcfg )
85     {
86       mAuthCfg = authcfg;
87     }
88     // avoid duplicate call to loadConfig(), which may potentially be triggered by combo box index changes in the
89     // call to populateConfigSelector(). We *always* call loadConfig() after this, so we don't want to do it twice.
90     mTemporarilyBlockLoad = true;
91     populateConfigSelector();
92     mTemporarilyBlockLoad = false;
93     loadConfig();
94   }
95 }
96 
setDataProviderKey(const QString & key)97 void QgsAuthConfigSelect::setDataProviderKey( const QString &key )
98 {
99   if ( mDisabled )
100   {
101     return;
102   }
103 
104   mDataProvider = key;
105   populateConfigSelector();
106 }
107 
loadConfig()108 void QgsAuthConfigSelect::loadConfig()
109 {
110   clearConfig();
111   if ( !mAuthCfg.isEmpty() && mConfigs.contains( mAuthCfg ) )
112   {
113     const QgsAuthMethodConfig config = mConfigs.value( mAuthCfg );
114     const QString authMethodKey = QgsApplication::authManager()->configAuthMethodKey( mAuthCfg );
115     QString methoddesc = tr( "Missing authentication method description" );
116     const QgsAuthMethodMetadata *meta = QgsApplication::authManager()->authMethodMetadata( authMethodKey );
117     if ( meta )
118     {
119       methoddesc = meta->description();
120     }
121     cmbConfigSelect->setToolTip( tr( "<ul><li><b>Method type:</b> %1</li>"
122                                      "<li><b>Configuration ID:</b> %2</li></ul>" ).arg( methoddesc, config.id( ) ) );
123     btnConfigEdit->setEnabled( true );
124     btnConfigRemove->setEnabled( true );
125   }
126   emit selectedConfigIdChanged( mAuthCfg );
127 }
128 
clearConfig()129 void QgsAuthConfigSelect::clearConfig()
130 {
131   cmbConfigSelect->setToolTip( QString() );
132   btnConfigEdit->setEnabled( false );
133   btnConfigRemove->setEnabled( false );
134 }
135 
validateConfig()136 void QgsAuthConfigSelect::validateConfig()
137 {
138   if ( !mAuthCfg.isEmpty() && !mConfigs.contains( mAuthCfg ) )
139   {
140     showMessage( tr( "Configuration '%1' not in database" ).arg( mAuthCfg ) );
141     mAuthCfg.clear();
142   }
143 }
144 
populateConfigSelector()145 void QgsAuthConfigSelect::populateConfigSelector()
146 {
147   loadAvailableConfigs();
148   validateConfig();
149 
150   cmbConfigSelect->blockSignals( true );
151   cmbConfigSelect->clear();
152   cmbConfigSelect->addItem( tr( "No Authentication" ), "0" );
153 
154   QgsStringMap sortmap;
155   QgsAuthMethodConfigsMap::const_iterator cit = mConfigs.constBegin();
156   for ( cit = mConfigs.constBegin(); cit != mConfigs.constEnd(); ++cit )
157   {
158     const QgsAuthMethodConfig config = cit.value();
159     sortmap.insert( QStringLiteral( "%1 (%2)" ).arg( config.name(), config.method() ), cit.key() );
160   }
161 
162   QgsStringMap::const_iterator sm = sortmap.constBegin();
163   for ( sm = sortmap.constBegin(); sm != sortmap.constEnd(); ++sm )
164   {
165     cmbConfigSelect->addItem( sm.key(), sm.value() );
166   }
167   cmbConfigSelect->blockSignals( false );
168 
169   int indx = 0;
170   if ( !mAuthCfg.isEmpty() )
171   {
172     indx = cmbConfigSelect->findData( mAuthCfg );
173   }
174   cmbConfigSelect->setCurrentIndex( indx > 0 ? indx : 0 );
175 }
176 
showMessage(const QString & msg)177 void QgsAuthConfigSelect::showMessage( const QString &msg )
178 {
179   if ( mDisabled )
180   {
181     return;
182   }
183   leConfigMsg->setText( msg );
184   frConfigMsg->setVisible( true );
185 }
186 
clearMessage()187 void QgsAuthConfigSelect::clearMessage()
188 {
189   if ( mDisabled )
190   {
191     return;
192   }
193   leConfigMsg->clear();
194   frConfigMsg->setVisible( false );
195 }
196 
loadAvailableConfigs()197 void QgsAuthConfigSelect::loadAvailableConfigs()
198 {
199   mConfigs.clear();
200   mConfigs = QgsApplication::authManager()->availableAuthMethodConfigs( mDataProvider );
201 }
202 
cmbConfigSelect_currentIndexChanged(int index)203 void QgsAuthConfigSelect::cmbConfigSelect_currentIndexChanged( int index )
204 {
205   const QString authcfg = cmbConfigSelect->itemData( index ).toString();
206   mAuthCfg = ( !authcfg.isEmpty() && authcfg != QLatin1String( "0" ) ) ? authcfg : QString();
207   if ( !mTemporarilyBlockLoad )
208     loadConfig();
209 }
210 
btnConfigAdd_clicked()211 void QgsAuthConfigSelect::btnConfigAdd_clicked()
212 {
213   if ( !QgsApplication::authManager()->setMasterPassword( true ) )
214     return;
215 
216   QgsAuthConfigEdit *ace = new QgsAuthConfigEdit( this, QString(), mDataProvider );
217   ace->setWindowModality( Qt::WindowModal );
218   if ( ace->exec() )
219   {
220     setConfigId( ace->configId() );
221   }
222   ace->deleteLater();
223 }
224 
btnConfigEdit_clicked()225 void QgsAuthConfigSelect::btnConfigEdit_clicked()
226 {
227   if ( !QgsApplication::authManager()->setMasterPassword( true ) )
228     return;
229 
230   QgsAuthConfigEdit *ace = new QgsAuthConfigEdit( this, mAuthCfg, mDataProvider );
231   ace->setWindowModality( Qt::WindowModal );
232   if ( ace->exec() )
233   {
234     //qDebug( "Edit returned config Id: %s", ace->configId().toLatin1().constData() );
235     setConfigId( ace->configId() );
236   }
237   ace->deleteLater();
238 }
239 
btnConfigRemove_clicked()240 void QgsAuthConfigSelect::btnConfigRemove_clicked()
241 {
242   if ( QMessageBox::warning( this, tr( "Remove Authentication" ),
243                              tr( "Are you sure that you want to permanently remove this configuration right now?\n\n"
244                                  "Operation can NOT be undone!" ),
245                              QMessageBox::Ok | QMessageBox::Cancel,
246                              QMessageBox::Cancel ) == QMessageBox::Cancel )
247   {
248     return;
249   }
250 
251   if ( QgsApplication::authManager()->removeAuthenticationConfig( mAuthCfg ) )
252   {
253     emit selectedConfigIdRemoved( mAuthCfg );
254     setConfigId( QString() );
255   }
256 }
257 
btnConfigMsgClear_clicked()258 void QgsAuthConfigSelect::btnConfigMsgClear_clicked()
259 {
260   clearMessage();
261 }
262 
263 
264 //////////////// Embed in dialog ///////////////////
265 
266 #include <QPushButton>
267 
QgsAuthConfigUriEdit(QWidget * parent,const QString & datauri,const QString & dataprovider)268 QgsAuthConfigUriEdit::QgsAuthConfigUriEdit( QWidget *parent, const QString &datauri, const QString &dataprovider )
269   : QDialog( parent )
270 {
271   if ( QgsApplication::authManager()->isDisabled() )
272   {
273     mDisabled = true;
274     mAuthNotifyLayout = new QVBoxLayout;
275     this->setLayout( mAuthNotifyLayout );
276     mAuthNotify = new QLabel( QgsApplication::authManager()->disabledMessage(), this );
277     mAuthNotifyLayout->addWidget( mAuthNotify );
278   }
279   else
280   {
281     setupUi( this );
282 
283     setWindowTitle( tr( "Authentication Config ID String Editor" ) );
284 
285     buttonBox->button( QDialogButtonBox::Close )->setDefault( true );
286     connect( buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close );
287     connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsAuthConfigUriEdit::saveChanges );
288 
289     connect( buttonBox->button( QDialogButtonBox::Reset ), &QAbstractButton::clicked, this, &QgsAuthConfigUriEdit::resetChanges );
290 
291     connect( wdgtAuthSelect, &QgsAuthConfigSelect::selectedConfigIdChanged, this, &QgsAuthConfigUriEdit::authCfgUpdated );
292     connect( wdgtAuthSelect, &QgsAuthConfigSelect::selectedConfigIdRemoved, this, &QgsAuthConfigUriEdit::authCfgRemoved );
293 
294     wdgtAuthSelect->setDataProviderKey( dataprovider );
295     setDataSourceUri( datauri );
296   }
297 }
298 
setDataSourceUri(const QString & datauri)299 void QgsAuthConfigUriEdit::setDataSourceUri( const QString &datauri )
300 {
301   if ( mDisabled )
302   {
303     return;
304   }
305   if ( datauri.isEmpty() )
306     return;
307 
308   mDataUri = mDataUriOrig = datauri;
309 
310   teDataUri->setPlainText( mDataUri );
311 
312   if ( authCfgIndex() == -1 )
313   {
314     wdgtAuthSelect->showMessage( tr( "No authcfg in Data Source URI" ) );
315     return;
316   }
317 
318   selectAuthCfgInUri();
319 
320   mAuthCfg = authCfgFromUri();
321 
322   QgsDebugMsg( QStringLiteral( "Parsed authcfg ID: %1" ).arg( mAuthCfg ) );
323 
324   wdgtAuthSelect->blockSignals( true );
325   wdgtAuthSelect->setConfigId( mAuthCfg );
326   wdgtAuthSelect->blockSignals( false );
327 }
328 
dataSourceUri()329 QString QgsAuthConfigUriEdit::dataSourceUri()
330 {
331   if ( mDisabled )
332   {
333     return QString();
334   }
335   return mDataUri;
336 }
337 
hasConfigId(const QString & txt)338 bool QgsAuthConfigUriEdit::hasConfigId( const QString &txt )
339 {
340   if ( QgsApplication::authManager()->isDisabled() )
341   {
342     return false;
343   }
344   return QgsApplication::authManager()->hasConfigId( txt );
345 }
346 
saveChanges()347 void QgsAuthConfigUriEdit::saveChanges()
348 {
349   this->accept();
350 }
351 
resetChanges()352 void QgsAuthConfigUriEdit::resetChanges()
353 {
354   wdgtAuthSelect->clearMessage();
355   setDataSourceUri( mDataUriOrig );
356 }
357 
authCfgUpdated(const QString & authcfg)358 void QgsAuthConfigUriEdit::authCfgUpdated( const QString &authcfg )
359 {
360   mAuthCfg = authcfg;
361 
362   if ( mAuthCfg.size() != 7 )
363   {
364     mAuthCfg.clear();
365     removeAuthCfgFromUri();
366   }
367   else
368   {
369     updateUriWithAuthCfg();
370   }
371   teDataUri->clear();
372   teDataUri->setPlainText( mDataUri );
373   selectAuthCfgInUri();
374 }
375 
authCfgRemoved(const QString & authcfg)376 void QgsAuthConfigUriEdit::authCfgRemoved( const QString &authcfg )
377 {
378   if ( authCfgFromUri() == authcfg )
379   {
380     removeAuthCfgFromUri();
381   }
382 }
383 
authCfgIndex()384 int QgsAuthConfigUriEdit::authCfgIndex()
385 {
386   return mDataUri.indexOf( QRegularExpression( QgsApplication::authManager()->configIdRegex() ) );
387 }
388 
authCfgFromUri()389 QString QgsAuthConfigUriEdit::authCfgFromUri()
390 {
391   const int startindex = authCfgIndex();
392   if ( startindex == -1 )
393     return QString();
394 
395   return mDataUri.mid( startindex + 8, 7 );
396 }
397 
selectAuthCfgInUri()398 void QgsAuthConfigUriEdit::selectAuthCfgInUri()
399 {
400   const int startindex = authCfgIndex();
401   if ( startindex == -1 )
402     return;
403 
404   // authcfg=.{7} will always be 15 chars
405   QTextCursor tc = teDataUri->textCursor();
406   tc.setPosition( startindex );
407   tc.setPosition( startindex + 15, QTextCursor::KeepAnchor );
408   teDataUri->setTextCursor( tc );
409   teDataUri->setFocus();
410 }
411 
updateUriWithAuthCfg()412 void QgsAuthConfigUriEdit::updateUriWithAuthCfg()
413 {
414   const int startindex = authCfgIndex();
415   if ( startindex == -1 )
416   {
417     if ( mAuthCfg.size() == 7 )
418     {
419       wdgtAuthSelect->showMessage( tr( "Adding authcfg to URI not supported" ) );
420     }
421     return;
422   }
423 
424   mDataUri = mDataUri.replace( startindex + 8, 7, mAuthCfg );
425 }
426 
removeAuthCfgFromUri()427 void QgsAuthConfigUriEdit::removeAuthCfgFromUri()
428 {
429   int startindex = authCfgIndex();
430   if ( startindex == -1 )
431     return;
432 
433   // add any preceding space so two spaces will not result after removal
434   int rmvlen = 15;
435   if ( startindex - 1 >= 0
436        && ( mDataUri.at( startindex - 1 ).isSpace()
437             || mDataUri.at( startindex - 1 ) == QChar( '&' ) ) )
438   {
439     startindex -= 1;
440     rmvlen += 1;
441   }
442 
443   // trim any leftover spaces or & from ends
444   mDataUri = mDataUri.remove( startindex, rmvlen ).trimmed();
445   if ( mDataUri.at( 0 ) == QChar( '&' ) )
446     mDataUri = mDataUri.remove( 0, 1 );
447 
448   // trim any & from
449 
450   mAuthCfg.clear();
451 }
452 
453