1 /***************************************************************************
2     qgsauthbasicmethod.cpp
3     ---------------------
4     begin                : September 1, 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 #include "qgsauthbasicmethod.h"
18 
19 #include "qgsauthmanager.h"
20 #include "qgslogger.h"
21 #include "qgsapplication.h"
22 
23 #ifdef HAVE_GUI
24 #include "qgsauthbasicedit.h"
25 #endif
26 
27 #include <QNetworkProxy>
28 #include <QMutexLocker>
29 #include <QRegularExpression>
30 #include <QUuid>
31 
32 const QString QgsAuthBasicMethod::AUTH_METHOD_KEY = QStringLiteral( "Basic" );
33 const QString QgsAuthBasicMethod::AUTH_METHOD_DESCRIPTION = QStringLiteral( "Basic authentication" );
34 const QString QgsAuthBasicMethod::AUTH_METHOD_DISPLAY_DESCRIPTION = tr( "Basic authentication" );
35 
36 QMap<QString, QgsAuthMethodConfig> QgsAuthBasicMethod::sAuthConfigCache = QMap<QString, QgsAuthMethodConfig>();
37 
38 
QgsAuthBasicMethod()39 QgsAuthBasicMethod::QgsAuthBasicMethod()
40 {
41   setVersion( 2 );
42   setExpansions( QgsAuthMethod::NetworkRequest | QgsAuthMethod::DataSourceUri );
43   setDataProviders( QStringList()
44                     << QStringLiteral( "postgres" )
45                     << QStringLiteral( "oracle" )
46                     << QStringLiteral( "db2" )
47                     << QStringLiteral( "ows" )
48                     << QStringLiteral( "wfs" )  // convert to lowercase
49                     << QStringLiteral( "wcs" )
50                     << QStringLiteral( "wms" )
51                     << QStringLiteral( "ogr" )
52                     << QStringLiteral( "gdal" )
53                     << QStringLiteral( "proxy" ) );
54 
55 }
56 
key() const57 QString QgsAuthBasicMethod::key() const
58 {
59   return AUTH_METHOD_KEY;
60 }
61 
description() const62 QString QgsAuthBasicMethod::description() const
63 {
64   return AUTH_METHOD_DESCRIPTION;
65 }
66 
displayDescription() const67 QString QgsAuthBasicMethod::displayDescription() const
68 {
69   return AUTH_METHOD_DISPLAY_DESCRIPTION;
70 }
71 
72 
updateNetworkRequest(QNetworkRequest & request,const QString & authcfg,const QString & dataprovider)73 bool QgsAuthBasicMethod::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
74     const QString &dataprovider )
75 {
76   Q_UNUSED( dataprovider )
77   const QgsAuthMethodConfig mconfig = getMethodConfig( authcfg );
78   if ( !mconfig.isValid() )
79   {
80     QgsDebugMsg( QStringLiteral( "Update request config FAILED for authcfg: %1: config invalid" ).arg( authcfg ) );
81     return false;
82   }
83 
84   const QString username = mconfig.config( QStringLiteral( "username" ) );
85   const QString password = mconfig.config( QStringLiteral( "password" ) );
86 
87   if ( !username.isEmpty() )
88   {
89     request.setRawHeader( "Authorization", "Basic " + QStringLiteral( "%1:%2" ).arg( username, password ).toUtf8().toBase64() );
90   }
91   return true;
92 }
93 
updateDataSourceUriItems(QStringList & connectionItems,const QString & authcfg,const QString & dataprovider)94 bool QgsAuthBasicMethod::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
95     const QString &dataprovider )
96 {
97   Q_UNUSED( dataprovider )
98   const QMutexLocker locker( &mMutex );
99   const QgsAuthMethodConfig mconfig = getMethodConfig( authcfg );
100   if ( !mconfig.isValid() )
101   {
102     QgsDebugMsg( QStringLiteral( "Update URI items FAILED for authcfg: %1: basic config invalid" ).arg( authcfg ) );
103     return false;
104   }
105 
106   const QString username = mconfig.config( QStringLiteral( "username" ) );
107   const QString password = mconfig.config( QStringLiteral( "password" ) );
108 
109   if ( username.isEmpty() )
110   {
111     QgsDebugMsg( QStringLiteral( "Update URI items FAILED for authcfg: %1: username empty" ).arg( authcfg ) );
112     return false;
113   }
114 
115   QString sslMode = QStringLiteral( "prefer" );
116   const thread_local QRegularExpression sslModeRegExp( "^sslmode=.*" );
117   const int sslModeIdx = connectionItems.indexOf( sslModeRegExp );
118   if ( sslModeIdx != -1 )
119   {
120     sslMode = connectionItems.at( sslModeIdx ).split( '=' ).at( 1 );
121   }
122 
123   // SSL Extra CAs
124   QString caparam;
125   QList<QSslCertificate> cas;
126   if ( sslMode.startsWith( QLatin1String( "verify-" ) ) )
127   {
128     cas = QgsApplication::authManager()->trustedCaCerts();
129     // save CAs to temp file
130     const QString tempFileBase = QStringLiteral( "tmp_basic_%1.pem" );
131     const QString caFilePath = QgsAuthCertUtils::pemTextToTempFile(
132                                  tempFileBase.arg( QUuid::createUuid().toString() ),
133                                  QgsAuthCertUtils::certsToPemText( cas ) );
134     if ( ! caFilePath.isEmpty() )
135     {
136       caparam = "sslrootcert='" + caFilePath + "'";
137     }
138   }
139 
140   // Branch for OGR
141   if ( dataprovider == QLatin1String( "ogr" ) || dataprovider == QLatin1String( "gdal" ) )
142   {
143     if ( ! password.isEmpty() )
144     {
145       const QString fullUri( connectionItems.first() );
146       QString uri( fullUri );
147       // Handle sub-layers
148       if ( fullUri.contains( '|' ) )
149       {
150         uri = uri.left( uri.indexOf( '|' ) );
151       }
152       // At least username must be set... password can be empty
153       if ( ! username.isEmpty() )
154       {
155         // Inject credentials
156         if ( uri.startsWith( QLatin1String( "PG:" ) ) )
157         {
158           bool chopped = false;
159           if ( uri.endsWith( '"' ) )
160           {
161             uri.chop( 1 );
162             chopped = true;
163           }
164           uri += QStringLiteral( " user='%1'" ).arg( username );
165           uri += QStringLiteral( " password='%1'" ).arg( password );
166           // add extra CAs
167           if ( ! caparam.isEmpty() )
168           {
169             uri += ' ' + caparam;
170           }
171           if ( chopped )
172             uri += '"';
173         }
174         else if ( uri.startsWith( QLatin1String( "SDE:" ) ) )
175         {
176           uri = uri.replace( QRegularExpression( ",$" ), QStringLiteral( ",%1,%2" ).arg( username, password ) );
177         }
178         else if ( uri.startsWith( QLatin1String( "IDB" ) ) )
179         {
180           bool chopped = false;
181           if ( uri.endsWith( '"' ) )
182           {
183             uri.chop( 1 );
184             chopped = true;
185           }
186           uri += QStringLiteral( " user=%1" ).arg( username );
187           uri += QStringLiteral( " pass=%1" ).arg( password );
188           if ( chopped )
189             uri += '"';
190         }
191         else if ( uri.startsWith( QLatin1String( "@driver=ingres" ) ) )
192         {
193           uri += QStringLiteral( ",userid=%1" ).arg( username );
194           uri += QStringLiteral( ",password=%1" ).arg( password );
195         }
196         else if ( uri.startsWith( QLatin1String( "MySQL:" ) ) )
197         {
198           uri += QStringLiteral( ",user=%1" ).arg( username );
199           uri += QStringLiteral( ",password=%1" ).arg( password );
200         }
201         else if ( uri.startsWith( QLatin1String( "MSSQL:" ) ) )
202         {
203           uri += QStringLiteral( ";uid=%1" ).arg( username );
204           uri = uri.replace( QLatin1String( ";trusted_connection=yes" ), QString() );
205           uri += QStringLiteral( ";pwd=%1" ).arg( password );
206         }
207         else if ( uri.startsWith( QLatin1String( "OCI:" ) ) )
208         {
209           // OCI:userid/password@database_instance:table,table
210           uri = uri.replace( QLatin1String( "OCI:/" ),  QStringLiteral( "OCI:%1/%2" ).arg( username, password ) );
211         }
212         else if ( uri.startsWith( QLatin1String( "ODBC:" ) ) )
213         {
214           uri = uri.replace( QRegularExpression( "^ODBC:@?" ), "ODBC:" + username + '/' + password + '@' );
215         }
216         else if ( uri.startsWith( QLatin1String( "couchdb" ) )
217                   || uri.startsWith( QLatin1String( "DODS" ) )
218                   || uri.startsWith( "http://" )
219                   || uri.startsWith( "/vsicurl/http://" )
220                   || uri.startsWith( "https://" )
221                   || uri.startsWith( "/vsicurl/https://" )
222                   || uri.startsWith( "ftp://" )
223                   || uri.startsWith( "/vsicurl/ftp://" )
224                 )
225         {
226           uri = uri.replace( QLatin1String( "://" ), QStringLiteral( "://%1:%2@" ).arg( username, password ) );
227         }
228       }
229       // Handle sub-layers
230       if ( fullUri.contains( '|' ) )
231       {
232         uri += '|' + fullUri.right( fullUri.length() - fullUri.lastIndexOf( '|' ) - 1 );
233       }
234       connectionItems.replace( 0, uri );
235     }
236     else
237     {
238       QgsDebugMsg( QStringLiteral( "Update URI items FAILED for authcfg: %1: password empty" ).arg( authcfg ) );
239     }
240 
241   }
242   else // Not-ogr
243   {
244     const QString userparam = "user='" + escapeUserPass( username ) + '\'';
245     const thread_local QRegularExpression userRegExp( "^user='.*" );
246     const int userindx = connectionItems.indexOf( userRegExp );
247     if ( userindx != -1 )
248     {
249       connectionItems.replace( userindx, userparam );
250     }
251     else
252     {
253       connectionItems.append( userparam );
254     }
255 
256     const QString passparam = "password='" + escapeUserPass( password ) + '\'';
257     const thread_local QRegularExpression passRegExp( "^password='.*" );
258     const int passindx = connectionItems.indexOf( passRegExp );
259     if ( passindx != -1 )
260     {
261       connectionItems.replace( passindx, passparam );
262     }
263     else
264     {
265       connectionItems.append( passparam );
266     }
267     // add extra CAs
268     if ( ! caparam.isEmpty() )
269     {
270       const thread_local QRegularExpression sslcaRegExp( "^sslrootcert='.*" );
271       const int sslcaindx = connectionItems.indexOf( sslcaRegExp );
272       if ( sslcaindx != -1 )
273       {
274         connectionItems.replace( sslcaindx, caparam );
275       }
276       else
277       {
278         connectionItems.append( caparam );
279       }
280     }
281   }
282 
283 
284   return true;
285 }
286 
updateNetworkProxy(QNetworkProxy & proxy,const QString & authcfg,const QString & dataprovider)287 bool QgsAuthBasicMethod::updateNetworkProxy( QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider )
288 {
289   Q_UNUSED( dataprovider )
290   const QMutexLocker locker( &mMutex );
291 
292   const QgsAuthMethodConfig mconfig = getMethodConfig( authcfg );
293   if ( !mconfig.isValid() )
294   {
295     QgsDebugMsg( QStringLiteral( "Update proxy config FAILED for authcfg: %1: config invalid" ).arg( authcfg ) );
296     return false;
297   }
298 
299   const QString username = mconfig.config( QStringLiteral( "username" ) );
300   const QString password = mconfig.config( QStringLiteral( "password" ) );
301 
302   if ( !username.isEmpty() )
303   {
304     proxy.setUser( username );
305     proxy.setPassword( password );
306   }
307   return true;
308 }
309 
updateMethodConfig(QgsAuthMethodConfig & mconfig)310 void QgsAuthBasicMethod::updateMethodConfig( QgsAuthMethodConfig &mconfig )
311 {
312   const QMutexLocker locker( &mMutex );
313   if ( mconfig.hasConfig( QStringLiteral( "oldconfigstyle" ) ) )
314   {
315     QgsDebugMsg( QStringLiteral( "Updating old style auth method config" ) );
316 
317     const QStringList conflist = mconfig.config( QStringLiteral( "oldconfigstyle" ) ).split( QStringLiteral( "|||" ) );
318     mconfig.setConfig( QStringLiteral( "realm" ), conflist.at( 0 ) );
319     mconfig.setConfig( QStringLiteral( "username" ), conflist.at( 1 ) );
320     mconfig.setConfig( QStringLiteral( "password" ), conflist.at( 2 ) );
321     mconfig.removeConfig( QStringLiteral( "oldconfigstyle" ) );
322   }
323 
324   // TODO: add updates as method version() increases due to config storage changes
325 }
326 
327 #ifdef HAVE_GUI
editWidget(QWidget * parent) const328 QWidget *QgsAuthBasicMethod::editWidget( QWidget *parent ) const
329 {
330   return new QgsAuthBasicEdit( parent );
331 }
332 #endif
333 
clearCachedConfig(const QString & authcfg)334 void QgsAuthBasicMethod::clearCachedConfig( const QString &authcfg )
335 {
336   removeMethodConfig( authcfg );
337 }
338 
getMethodConfig(const QString & authcfg,bool fullconfig)339 QgsAuthMethodConfig QgsAuthBasicMethod::getMethodConfig( const QString &authcfg, bool fullconfig )
340 {
341   const QMutexLocker locker( &mMutex );
342   QgsAuthMethodConfig mconfig;
343 
344   // check if it is cached
345   if ( sAuthConfigCache.contains( authcfg ) )
346   {
347     mconfig = sAuthConfigCache.value( authcfg );
348     QgsDebugMsg( QStringLiteral( "Retrieved config for authcfg: %1" ).arg( authcfg ) );
349     return mconfig;
350   }
351 
352   // else build basic bundle
353   if ( !QgsApplication::authManager()->loadAuthenticationConfig( authcfg, mconfig, fullconfig ) )
354   {
355     QgsDebugMsg( QStringLiteral( "Retrieve config FAILED for authcfg: %1" ).arg( authcfg ) );
356     return QgsAuthMethodConfig();
357   }
358 
359   // cache bundle
360   putMethodConfig( authcfg, mconfig );
361 
362   return mconfig;
363 }
364 
putMethodConfig(const QString & authcfg,const QgsAuthMethodConfig & mconfig)365 void QgsAuthBasicMethod::putMethodConfig( const QString &authcfg, const QgsAuthMethodConfig &mconfig )
366 {
367   const QMutexLocker locker( &mMutex );
368   QgsDebugMsg( QStringLiteral( "Putting basic config for authcfg: %1" ).arg( authcfg ) );
369   sAuthConfigCache.insert( authcfg, mconfig );
370 }
371 
removeMethodConfig(const QString & authcfg)372 void QgsAuthBasicMethod::removeMethodConfig( const QString &authcfg )
373 {
374   const QMutexLocker locker( &mMutex );
375   if ( sAuthConfigCache.contains( authcfg ) )
376   {
377     sAuthConfigCache.remove( authcfg );
378     QgsDebugMsg( QStringLiteral( "Removed basic config for authcfg: %1" ).arg( authcfg ) );
379   }
380 }
381 
escapeUserPass(const QString & val,QChar delim) const382 QString QgsAuthBasicMethod::escapeUserPass( const QString &val, QChar delim ) const
383 {
384   QString escaped = val;
385 
386   escaped.replace( '\\', QLatin1String( "\\\\" ) );
387   escaped.replace( delim, QStringLiteral( "\\%1" ).arg( delim ) );
388 
389   return escaped;
390 }
391 
392 //////////////////////////////////////////////
393 // Plugin externals
394 //////////////////////////////////////////////
395 
396 
397 #ifndef HAVE_STATIC_PROVIDERS
authMethodMetadataFactory()398 QGISEXTERN QgsAuthMethodMetadata *authMethodMetadataFactory()
399 {
400   return new QgsAuthBasicMethodMetadata();
401 }
402 #endif
403 
404 
405 
406