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