1 /***************************************************************************
2     begin                : July 30, 2016
3     copyright            : (C) 2016 by Monsanto Company, USA
4     author               : Larry Shaffer, Boundless Spatial
5     email                : lshaffer at boundlessgeo dot com
6  ***************************************************************************
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  ***************************************************************************/
14 #include <functional>
15 
16 #include "qgsauthoauth2config.h"
17 
18 #include <QDir>
19 
20 #include "Json.h"
21 
22 #include "qgsapplication.h"
23 #include "qgslogger.h"
24 
25 
QgsAuthOAuth2Config(QObject * parent)26 QgsAuthOAuth2Config::QgsAuthOAuth2Config( QObject *parent )
27   : QObject( parent )
28   , mQueryPairs( QVariantMap() )
29 {
30 
31   // internal signal bounces
32   connect( this, &QgsAuthOAuth2Config::idChanged, this, &QgsAuthOAuth2Config::configChanged );
33   connect( this, &QgsAuthOAuth2Config::versionChanged, this, &QgsAuthOAuth2Config::configChanged );
34   connect( this, &QgsAuthOAuth2Config::configTypeChanged, this, &QgsAuthOAuth2Config::configChanged );
35   connect( this, &QgsAuthOAuth2Config::grantFlowChanged, this, &QgsAuthOAuth2Config::configChanged );
36   connect( this, &QgsAuthOAuth2Config::nameChanged, this, &QgsAuthOAuth2Config::configChanged );
37   connect( this, &QgsAuthOAuth2Config::descriptionChanged, this, &QgsAuthOAuth2Config::configChanged );
38   connect( this, &QgsAuthOAuth2Config::requestUrlChanged, this, &QgsAuthOAuth2Config::configChanged );
39   connect( this, &QgsAuthOAuth2Config::tokenUrlChanged, this, &QgsAuthOAuth2Config::configChanged );
40   connect( this, &QgsAuthOAuth2Config::refreshTokenUrlChanged, this, &QgsAuthOAuth2Config::configChanged );
41   connect( this, &QgsAuthOAuth2Config::redirectUrlChanged, this, &QgsAuthOAuth2Config::configChanged );
42   connect( this, &QgsAuthOAuth2Config::redirectPortChanged, this, &QgsAuthOAuth2Config::configChanged );
43   connect( this, &QgsAuthOAuth2Config::clientIdChanged, this, &QgsAuthOAuth2Config::configChanged );
44   connect( this, &QgsAuthOAuth2Config::clientSecretChanged, this, &QgsAuthOAuth2Config::configChanged );
45   connect( this, &QgsAuthOAuth2Config::usernameChanged, this, &QgsAuthOAuth2Config::configChanged );
46   connect( this, &QgsAuthOAuth2Config::passwordChanged, this, &QgsAuthOAuth2Config::configChanged );
47   connect( this, &QgsAuthOAuth2Config::scopeChanged, this, &QgsAuthOAuth2Config::configChanged );
48   connect( this, &QgsAuthOAuth2Config::apiKeyChanged, this, &QgsAuthOAuth2Config::configChanged );
49   connect( this, &QgsAuthOAuth2Config::persistTokenChanged, this, &QgsAuthOAuth2Config::configChanged );
50   connect( this, &QgsAuthOAuth2Config::accessMethodChanged, this, &QgsAuthOAuth2Config::configChanged );
51   connect( this, &QgsAuthOAuth2Config::requestTimeoutChanged, this, &QgsAuthOAuth2Config::configChanged );
52   connect( this, &QgsAuthOAuth2Config::queryPairsChanged, this, &QgsAuthOAuth2Config::configChanged );
53   connect( this, &QgsAuthOAuth2Config::customHeaderChanged, this, &QgsAuthOAuth2Config::configChanged );
54 
55   // always recheck validity on any change
56   // this, in turn, may emit validityChanged( bool )
57   connect( this, &QgsAuthOAuth2Config::configChanged, this, &QgsAuthOAuth2Config::validateConfig );
58 
59   validateConfig();
60 }
61 
62 
setId(const QString & value)63 void QgsAuthOAuth2Config::setId( const QString &value )
64 {
65   const QString preval( mId );
66   mId = value;
67   if ( preval != value )
68     emit idChanged( mId );
69 }
70 
setVersion(int value)71 void QgsAuthOAuth2Config::setVersion( int value )
72 {
73   const int preval( mVersion );
74   mVersion = value;
75   if ( preval != value )
76     emit versionChanged( mVersion );
77 }
78 
setConfigType(QgsAuthOAuth2Config::ConfigType value)79 void QgsAuthOAuth2Config::setConfigType( QgsAuthOAuth2Config::ConfigType value )
80 {
81   const ConfigType preval( mConfigType );
82   mConfigType = value;
83   if ( preval != value )
84     emit configTypeChanged( mConfigType );
85 }
86 
setGrantFlow(QgsAuthOAuth2Config::GrantFlow value)87 void QgsAuthOAuth2Config::setGrantFlow( QgsAuthOAuth2Config::GrantFlow value )
88 {
89   const GrantFlow preval( mGrantFlow );
90   mGrantFlow = value;
91   if ( preval != value )
92     emit grantFlowChanged( mGrantFlow );
93 }
94 
setName(const QString & value)95 void QgsAuthOAuth2Config::setName( const QString &value )
96 {
97   const QString preval( mName );
98   mName = value;
99   if ( preval != value )
100     emit nameChanged( mName );
101 }
102 
setDescription(const QString & value)103 void QgsAuthOAuth2Config::setDescription( const QString &value )
104 {
105   const QString preval( mDescription );
106   mDescription = value;
107   if ( preval != value )
108     emit descriptionChanged( mDescription );
109 }
110 
setRequestUrl(const QString & value)111 void QgsAuthOAuth2Config::setRequestUrl( const QString &value )
112 {
113   const QString preval( mRequestUrl );
114   mRequestUrl = value;
115   if ( preval != value )
116     emit requestUrlChanged( mRequestUrl );
117 }
118 
setTokenUrl(const QString & value)119 void QgsAuthOAuth2Config::setTokenUrl( const QString &value )
120 {
121   const QString preval( mTokenUrl );
122   mTokenUrl = value;
123   if ( preval != value )
124     emit tokenUrlChanged( mTokenUrl );
125 }
126 
setRefreshTokenUrl(const QString & value)127 void QgsAuthOAuth2Config::setRefreshTokenUrl( const QString &value )
128 {
129   const QString preval( mRefreshTokenUrl );
130   mRefreshTokenUrl = value;
131   if ( preval != value )
132     emit refreshTokenUrlChanged( mRefreshTokenUrl );
133 }
134 
setRedirectUrl(const QString & value)135 void QgsAuthOAuth2Config::setRedirectUrl( const QString &value )
136 {
137   const QString preval( mRedirectURL );
138   mRedirectURL = value;
139   if ( preval != value )
140     emit redirectUrlChanged( mRedirectURL );
141 }
142 
setRedirectPort(int value)143 void QgsAuthOAuth2Config::setRedirectPort( int value )
144 {
145   const int preval( mRedirectPort );
146   mRedirectPort = value;
147   if ( preval != value )
148     emit redirectPortChanged( mRedirectPort );
149 }
150 
setClientId(const QString & value)151 void QgsAuthOAuth2Config::setClientId( const QString &value )
152 {
153   const QString preval( mClientId );
154   mClientId = value;
155   if ( preval != value )
156     emit clientIdChanged( mClientId );
157 }
158 
setClientSecret(const QString & value)159 void QgsAuthOAuth2Config::setClientSecret( const QString &value )
160 {
161   const QString preval( mClientSecret );
162   mClientSecret = value;
163   if ( preval != value )
164     emit clientSecretChanged( mClientSecret );
165 }
166 
setUsername(const QString & value)167 void QgsAuthOAuth2Config::setUsername( const QString &value )
168 {
169   const QString preval( mUsername );
170   mUsername = value;
171   if ( preval != value )
172     emit usernameChanged( mUsername );
173 }
174 
setPassword(const QString & value)175 void QgsAuthOAuth2Config::setPassword( const QString &value )
176 {
177   const QString preval( mPassword );
178   mPassword = value;
179   if ( preval != value )
180     emit passwordChanged( mPassword );
181 }
182 
setScope(const QString & value)183 void QgsAuthOAuth2Config::setScope( const QString &value )
184 {
185   const QString preval( mScope );
186   mScope = value;
187   if ( preval != value )
188     emit scopeChanged( mScope );
189 }
190 
setApiKey(const QString & value)191 void QgsAuthOAuth2Config::setApiKey( const QString &value )
192 {
193   const QString preval( mApiKey );
194   mApiKey = value;
195   if ( preval != value )
196     emit apiKeyChanged( mApiKey );
197 }
198 
setPersistToken(bool persist)199 void QgsAuthOAuth2Config::setPersistToken( bool persist )
200 {
201   const bool preval( mPersistToken );
202   mPersistToken = persist;
203   if ( preval != persist )
204     emit persistTokenChanged( mPersistToken );
205 }
206 
setAccessMethod(QgsAuthOAuth2Config::AccessMethod value)207 void QgsAuthOAuth2Config::setAccessMethod( QgsAuthOAuth2Config::AccessMethod value )
208 {
209   const AccessMethod preval( mAccessMethod );
210   mAccessMethod = value;
211   if ( preval != value )
212     emit accessMethodChanged( mAccessMethod );
213 }
214 
setCustomHeader(const QString & header)215 void QgsAuthOAuth2Config::setCustomHeader( const QString &header )
216 {
217   const QString preval( mCustomHeader );
218   mCustomHeader = header;
219   if ( preval != header )
220     emit customHeaderChanged( mCustomHeader );
221 }
222 
setRequestTimeout(int value)223 void QgsAuthOAuth2Config::setRequestTimeout( int value )
224 {
225   const int preval( mRequestTimeout );
226   mRequestTimeout = value;
227   if ( preval != value )
228     emit requestTimeoutChanged( mRequestTimeout );
229 }
230 
setQueryPairs(const QVariantMap & pairs)231 void QgsAuthOAuth2Config::setQueryPairs( const QVariantMap &pairs )
232 {
233   const QVariantMap preval( mQueryPairs );
234   mQueryPairs = pairs;
235   if ( preval != pairs )
236     emit queryPairsChanged( mQueryPairs );
237 }
238 
setToDefaults()239 void QgsAuthOAuth2Config::setToDefaults()
240 {
241   setId( QString() );
242   setVersion( 1 );
243   setConfigType( QgsAuthOAuth2Config::Custom );
244   setGrantFlow( QgsAuthOAuth2Config::AuthCode );
245   setName( QString() );
246   setDescription( QString() );
247   setRequestUrl( QString() );
248   setTokenUrl( QString() );
249   setRefreshTokenUrl( QString() );
250   setRedirectUrl( QString() );
251   setRedirectPort( 7070 );
252   setClientId( QString() );
253   setClientSecret( QString() );
254   setUsername( QString() );
255   setPassword( QString() );
256   setScope( QString() );
257   setApiKey( QString() );
258   setPersistToken( false );
259   setAccessMethod( QgsAuthOAuth2Config::Header );
260   setCustomHeader( QString() );
261   setRequestTimeout( 30 ); // in seconds
262   setQueryPairs( QVariantMap() );
263 }
264 
operator ==(const QgsAuthOAuth2Config & other) const265 bool QgsAuthOAuth2Config::operator==( const QgsAuthOAuth2Config &other ) const
266 {
267   return ( other.version() == this->version()
268            && other.configType() == this->configType()
269            && other.grantFlow() == this->grantFlow()
270            && other.name() == this->name()
271            && other.description() == this->description()
272            && other.requestUrl() == this->requestUrl()
273            && other.tokenUrl() == this->tokenUrl()
274            && other.refreshTokenUrl() == this->refreshTokenUrl()
275            && other.redirectUrl() == this->redirectUrl()
276            && other.redirectPort() == this->redirectPort()
277            && other.clientId() == this->clientId()
278            && other.clientSecret() == this->clientSecret()
279            && other.username() == this->username()
280            && other.password() == this->password()
281            && other.scope() == this->scope()
282            && other.apiKey() == this->apiKey()
283            && other.persistToken() == this->persistToken()
284            && other.accessMethod() == this->accessMethod()
285            && other.customHeader() == this->customHeader()
286            && other.requestTimeout() == this->requestTimeout()
287            && other.queryPairs() == this->queryPairs() );
288 }
289 
operator !=(const QgsAuthOAuth2Config & other) const290 bool QgsAuthOAuth2Config::operator!=( const QgsAuthOAuth2Config &other ) const
291 {
292   return  !( *this == other );
293 }
294 
isValid() const295 bool QgsAuthOAuth2Config::isValid() const
296 {
297   return mValid;
298 }
299 
300 // slot
validateConfig()301 void QgsAuthOAuth2Config::validateConfig()
302 {
303   validateConfigId( false );
304 }
305 
306 // public
validateConfigId(bool needsId)307 void QgsAuthOAuth2Config::validateConfigId( bool needsId )
308 {
309   const bool oldvalid = mValid;
310 
311   if ( mGrantFlow == AuthCode || mGrantFlow == Implicit )
312   {
313     mValid = ( !requestUrl().isEmpty()
314                && !tokenUrl().isEmpty()
315                && !clientId().isEmpty()
316                && ( mGrantFlow == AuthCode ? !clientSecret().isEmpty() : true )
317                && redirectPort() > 0
318                && ( needsId ? !id().isEmpty() : true ) );
319   }
320   else if ( mGrantFlow == ResourceOwner )
321   {
322     mValid = ( !tokenUrl().isEmpty()
323                && !username().isEmpty()
324                && !password().isEmpty()
325                && ( needsId ? !id().isEmpty() : true ) );
326   }
327 
328   if ( mValid != oldvalid )
329     emit validityChanged( mValid );
330 }
331 
loadConfigTxt(const QByteArray & configtxt,QgsAuthOAuth2Config::ConfigFormat format)332 bool QgsAuthOAuth2Config::loadConfigTxt(
333   const QByteArray &configtxt, QgsAuthOAuth2Config::ConfigFormat format )
334 {
335   QByteArray errStr;
336   bool res = false;
337 
338   switch ( format )
339   {
340     case JSON:
341     {
342       const QVariant variant = QJsonWrapper::parseJson( configtxt, &res, &errStr );
343       if ( !res )
344       {
345         QgsDebugMsg( QStringLiteral( "Error parsing JSON: %1" ).arg( QString( errStr ) ) );
346         return res;
347       }
348       const QVariantMap variantMap = variant.toMap();
349       // safety check -- qvariant2qobject asserts if an non-matching property is found in the json
350       for ( QVariantMap::const_iterator iter = variantMap.constBegin(); iter != variantMap.constEnd(); ++iter )
351       {
352         const QVariant property = this->property( iter.key().toLatin1() );
353         if ( !property.isValid() ) // e.g. not a auth config json file
354           return false;
355       }
356 
357       QJsonWrapper::qvariant2qobject( variantMap, this );
358       break;
359     }
360     default:
361       QgsDebugMsg( QStringLiteral( "Unsupported output format" ) );
362   }
363   return true;
364 }
365 
saveConfigTxt(QgsAuthOAuth2Config::ConfigFormat format,bool pretty,bool * ok) const366 QByteArray QgsAuthOAuth2Config::saveConfigTxt(
367   QgsAuthOAuth2Config::ConfigFormat format, bool pretty, bool *ok ) const
368 {
369   QByteArray out;
370   QByteArray errStr;
371   bool res = false;
372 
373   if ( !isValid() )
374   {
375     QgsDebugMsg( QStringLiteral( "FAILED, config is not valid" ) );
376     if ( ok )
377       *ok = res;
378     return out;
379   }
380 
381   switch ( format )
382   {
383     case JSON:
384     {
385       const QVariantMap variant = QJsonWrapper::qobject2qvariant( this );
386       out = QJsonWrapper::toJson( variant, &res, &errStr, pretty );
387       if ( !res )
388       {
389         QgsDebugMsg( QStringLiteral( "Error serializing JSON: %1" ).arg( QString( errStr ) ) );
390       }
391       break;
392     }
393     default:
394       QgsDebugMsg( QStringLiteral( "Unsupported output format" ) );
395   }
396 
397   if ( ok )
398     *ok = res;
399   return out;
400 }
401 
mappedProperties() const402 QVariantMap QgsAuthOAuth2Config::mappedProperties() const
403 {
404   QVariantMap vmap;
405   vmap.insert( QStringLiteral( "apiKey" ), this->apiKey() );
406   vmap.insert( QStringLiteral( "clientId" ), this->clientId() );
407   vmap.insert( QStringLiteral( "clientSecret" ), this->clientSecret() );
408   vmap.insert( QStringLiteral( "configType" ), static_cast<int>( this->configType() ) );
409   vmap.insert( QStringLiteral( "description" ), this->description() );
410   vmap.insert( QStringLiteral( "grantFlow" ), static_cast<int>( this->grantFlow() ) );
411   vmap.insert( QStringLiteral( "id" ), this->id() );
412   vmap.insert( QStringLiteral( "name" ), this->name() );
413   vmap.insert( QStringLiteral( "password" ), this->password() );
414   vmap.insert( QStringLiteral( "persistToken" ), this->persistToken() );
415   vmap.insert( QStringLiteral( "queryPairs" ), this->queryPairs() );
416   vmap.insert( QStringLiteral( "redirectPort" ), this->redirectPort() );
417   vmap.insert( QStringLiteral( "redirectUrl" ), this->redirectUrl() );
418   vmap.insert( QStringLiteral( "refreshTokenUrl" ), this->refreshTokenUrl() );
419   vmap.insert( QStringLiteral( "accessMethod" ), static_cast<int>( this->accessMethod() ) );
420   vmap.insert( QStringLiteral( "customHeader" ), this->customHeader() );
421   vmap.insert( QStringLiteral( "requestTimeout" ), this->requestTimeout() );
422   vmap.insert( QStringLiteral( "requestUrl" ), this->requestUrl() );
423   vmap.insert( QStringLiteral( "scope" ), this->scope() );
424   vmap.insert( QStringLiteral( "tokenUrl" ), this->tokenUrl() );
425   vmap.insert( QStringLiteral( "username" ), this->username() );
426   vmap.insert( QStringLiteral( "version" ), this->version() );
427 
428   return vmap;
429 }
430 
431 // static
serializeFromVariant(const QVariantMap & variant,QgsAuthOAuth2Config::ConfigFormat format,bool pretty,bool * ok)432 QByteArray QgsAuthOAuth2Config::serializeFromVariant(
433   const QVariantMap &variant,
434   QgsAuthOAuth2Config::ConfigFormat format,
435   bool pretty,
436   bool *ok )
437 {
438   QByteArray out;
439   QByteArray errStr;
440   bool res = false;
441 
442   switch ( format )
443   {
444     case JSON:
445       out = QJsonWrapper::toJson( variant, &res, &errStr, pretty );
446       if ( !res )
447       {
448         QgsDebugMsg( QStringLiteral( "Error serializing JSON: %1" ).arg( QString( errStr ) ) );
449       }
450       break;
451     default:
452       QgsDebugMsg( QStringLiteral( "Unsupported output format" ) );
453   }
454 
455   if ( ok )
456     *ok = res;
457   return out;
458 }
459 
460 // static
variantFromSerialized(const QByteArray & serial,QgsAuthOAuth2Config::ConfigFormat format,bool * ok)461 QVariantMap QgsAuthOAuth2Config::variantFromSerialized(
462   const QByteArray &serial,
463   QgsAuthOAuth2Config::ConfigFormat format,
464   bool *ok )
465 {
466   QVariantMap vmap;
467   QByteArray errStr;
468   bool res = false;
469 
470   switch ( format )
471   {
472     case JSON:
473     {
474       const QVariant var = QJsonWrapper::parseJson( serial, &res, &errStr );
475       if ( !res )
476       {
477         QgsDebugMsg( QStringLiteral( "Error parsing JSON to variant: %1" ).arg( QString( errStr ) ) );
478         if ( ok )
479           *ok = res;
480         return vmap;
481       }
482 
483       if ( var.isNull() )
484       {
485         QgsDebugMsg( QStringLiteral( "Error parsing JSON to variant: %1" ).arg( "invalid or null" ) );
486         if ( ok )
487           *ok = res;
488         return vmap;
489       }
490       vmap = var.toMap();
491       if ( vmap.isEmpty() )
492       {
493         QgsDebugMsg( QStringLiteral( "Error parsing JSON to variantmap: %1" ).arg( "map empty" ) );
494         if ( ok )
495           *ok = res;
496         return vmap;
497       }
498       break;
499     }
500     default:
501       QgsDebugMsg( QStringLiteral( "Unsupported output format" ) );
502   }
503 
504   if ( ok )
505     *ok = res;
506   return vmap;
507 }
508 
509 //static
writeOAuth2Config(const QString & filepath,QgsAuthOAuth2Config * config,QgsAuthOAuth2Config::ConfigFormat format,bool pretty)510 bool QgsAuthOAuth2Config::writeOAuth2Config(
511   const QString &filepath,
512   QgsAuthOAuth2Config *config,
513   QgsAuthOAuth2Config::ConfigFormat format,
514   bool pretty )
515 {
516   bool res = false;
517   const QByteArray configtxt = config->saveConfigTxt( format, pretty, &res );
518   if ( !res )
519   {
520     QgsDebugMsg( QStringLiteral( "FAILED to save config to text" ) );
521     return false;
522   }
523 
524   QFile config_file( filepath );
525   const QString file_path( config_file.fileName() );
526 
527   if ( config_file.open( QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text ) )
528   {
529     const qint64 bytesWritten = config_file.write( configtxt );
530     config_file.close();
531     if ( bytesWritten == -1 )
532     {
533       QgsDebugMsg( QStringLiteral( "FAILED to write config file: %1" ).arg( file_path ) );
534       return false;
535     }
536   }
537   else
538   {
539     QgsDebugMsg( QStringLiteral( "FAILED to open for writing config file: %1" ).arg( file_path ) );
540     return false;
541   }
542 
543   if ( !config_file.setPermissions( QFile::ReadOwner | QFile::WriteOwner ) )
544   {
545     QgsDebugMsg( QStringLiteral( "FAILED to set permissions config file: %1" ).arg( file_path ) );
546     return false;
547   }
548 
549   return true;
550 }
551 
552 // static
loadOAuth2Configs(const QString & configdirectory,QObject * parent,QgsAuthOAuth2Config::ConfigFormat format,bool * ok)553 QList<QgsAuthOAuth2Config *> QgsAuthOAuth2Config::loadOAuth2Configs(
554   const QString &configdirectory,
555   QObject *parent,
556   QgsAuthOAuth2Config::ConfigFormat format,
557   bool *ok )
558 {
559   QList<QgsAuthOAuth2Config *> configs = QList<QgsAuthOAuth2Config *>();
560   const bool res = false;
561   QStringList namefilters;
562 
563   switch ( format )
564   {
565     case JSON:
566       namefilters << QStringLiteral( "*.json" );
567       break;
568     default:
569       QgsDebugMsg( QStringLiteral( "Unsupported output format" ) );
570       if ( ok )
571         *ok = res;
572       return configs;
573   }
574 
575   QDir configdir( configdirectory );
576   configdir.setNameFilters( namefilters );
577   const QStringList configfiles = configdir.entryList( namefilters );
578 
579   if ( configfiles.size() > 0 )
580   {
581     QgsDebugMsg( QStringLiteral( "Config files found in: %1...\n%2" )
582                  .arg( configdir.path(), configfiles.join( QLatin1String( ", " ) ) ) );
583   }
584   else
585   {
586     QgsDebugMsg( QStringLiteral( "No config files found in: %1" ).arg( configdir.path() ) );
587     if ( ok ) *ok = res;
588     return configs;
589   }
590 
591   // Add entries
592   for ( const auto &configfile : configfiles )
593   {
594     QByteArray configtxt;
595     QFile cfile( configdir.path() + '/' + configfile );
596     if ( cfile.exists() )
597     {
598       const bool ret = cfile.open( QIODevice::ReadOnly | QIODevice::Text );
599       if ( ret )
600       {
601         configtxt = cfile.readAll();
602       }
603       else
604       {
605         QgsDebugMsg( QStringLiteral( "FAILED to open config for reading: %1" ).arg( configfile ) );
606       }
607       cfile.close();
608     }
609 
610     if ( configtxt.isEmpty() )
611     {
612       QgsDebugMsg( QStringLiteral( "EMPTY read of config: %1" ).arg( configfile ) );
613       continue;
614     }
615 
616     QgsAuthOAuth2Config *config = new QgsAuthOAuth2Config( parent );
617     if ( !config->loadConfigTxt( configtxt, format ) )
618     {
619       QgsDebugMsg( QStringLiteral( "FAILED to load config: %1" ).arg( configfile ) );
620       config->deleteLater();
621       continue;
622     }
623     configs << config;
624   }
625 
626   if ( ok ) *ok = true;
627   return configs;
628 }
629 
630 // static
mapOAuth2Configs(const QString & configdirectory,QObject * parent,QgsAuthOAuth2Config::ConfigFormat format,bool * ok)631 QgsStringMap QgsAuthOAuth2Config::mapOAuth2Configs(
632   const QString &configdirectory,
633   QObject *parent,
634   QgsAuthOAuth2Config::ConfigFormat format,
635   bool *ok )
636 {
637   QgsStringMap configs = QgsStringMap();
638   const bool res = false;
639   QStringList namefilters;
640 
641   switch ( format )
642   {
643     case JSON:
644       namefilters << QStringLiteral( "*.json" );
645       break;
646     default:
647       QgsDebugMsg( QStringLiteral( "Unsupported output format" ) );
648       if ( ok )
649         *ok = res;
650       return configs;
651   }
652 
653   QDir configdir( configdirectory );
654   configdir.setNameFilters( namefilters );
655   const QStringList configfiles = configdir.entryList( namefilters );
656 
657   if ( configfiles.size() > 0 )
658   {
659     QgsDebugMsg( QStringLiteral( "Config files found in: %1...\n%2" )
660                  .arg( configdir.path(), configfiles.join( QLatin1String( ", " ) ) ) );
661   }
662   else
663   {
664     QgsDebugMsg( QStringLiteral( "No config files found in: %1" ).arg( configdir.path() ) );
665     if ( ok )
666       *ok = res;
667     return configs;
668   }
669 
670   // Add entries
671   for ( const auto &configfile : configfiles )
672   {
673     QByteArray configtxt;
674     QFile cfile( configdir.path() + '/' + configfile );
675     if ( cfile.exists() )
676     {
677       const bool ret = cfile.open( QIODevice::ReadOnly | QIODevice::Text );
678       if ( ret )
679       {
680         configtxt = cfile.readAll();
681       }
682       else
683       {
684         QgsDebugMsg( QStringLiteral( "FAILED to open config for reading: %1" ).arg( configfile ) );
685       }
686       cfile.close();
687     }
688 
689     if ( configtxt.isEmpty() )
690     {
691       QgsDebugMsg( QStringLiteral( "EMPTY read of config: %1" ).arg( configfile ) );
692       continue;
693     }
694 
695     // validate the config before caching it
696     std::unique_ptr<QgsAuthOAuth2Config, std::function<void( QgsAuthOAuth2Config * )> > config( new QgsAuthOAuth2Config( parent ), []( QgsAuthOAuth2Config * cfg ) { cfg->deleteLater( );} );
697     if ( !config->loadConfigTxt( configtxt, format ) )
698     {
699       QgsDebugMsg( QStringLiteral( "FAILED to load config: %1" ).arg( configfile ) );
700       continue;
701     }
702     if ( config->id().isEmpty() )
703     {
704       QgsDebugMsg( QStringLiteral( "NO ID SET for config: %1" ).arg( configfile ) );
705       continue;
706     }
707     configs.insert( config->id(), configtxt );
708   }
709 
710   if ( ok )
711     *ok = true;
712   return configs;
713 }
714 
configLocations(const QString & extradir)715 QStringList QgsAuthOAuth2Config::configLocations( const QString &extradir )
716 {
717   QStringList dirs;
718   // in order of override preference, i.e. user over pkg dir
719   dirs << QgsAuthOAuth2Config::oauth2ConfigsPkgDataDir()
720        << QgsAuthOAuth2Config::oauth2ConfigsUserSettingsDir();
721 
722   if ( !extradir.isEmpty() )
723   {
724     // configs of similar IDs in this dir will override existing in standard dirs
725     dirs << extradir;
726   }
727   return dirs;
728 }
729 
mappedOAuth2ConfigsCache(QObject * parent,const QString & extradir)730 QgsStringMap QgsAuthOAuth2Config::mappedOAuth2ConfigsCache( QObject *parent, const QString &extradir )
731 {
732   QgsStringMap configs;
733   bool ok = false;
734 
735   // Load from default locations
736   const QStringList configdirs = configLocations( extradir );
737   for ( const auto &configdir : configdirs )
738   {
739     const QFileInfo configdirinfo( configdir );
740     if ( !configdirinfo.exists() || !configdirinfo.isDir() )
741     {
742       continue;
743     }
744     const QgsStringMap newconfigs = QgsAuthOAuth2Config::mapOAuth2Configs(
745                                       configdirinfo.canonicalFilePath(), parent, QgsAuthOAuth2Config::JSON, &ok );
746     if ( ok )
747     {
748       QgsStringMap::const_iterator i = newconfigs.constBegin();
749       while ( i != newconfigs.constEnd() )
750       {
751         configs.insert( i.key(), i.value() );
752         ++i;
753       }
754     }
755   }
756   return configs;
757 }
758 
759 // static
oauth2ConfigsPkgDataDir()760 QString QgsAuthOAuth2Config::oauth2ConfigsPkgDataDir()
761 {
762   return QgsApplication::pkgDataPath() + QStringLiteral( "/oauth2_configs" );
763 }
764 
765 // static
oauth2ConfigsUserSettingsDir()766 QString QgsAuthOAuth2Config::oauth2ConfigsUserSettingsDir()
767 {
768   return QgsApplication::qgisSettingsDirPath() + QStringLiteral( "oauth2_configs" );
769 }
770 
771 // static
configTypeString(QgsAuthOAuth2Config::ConfigType configtype)772 QString QgsAuthOAuth2Config::configTypeString( QgsAuthOAuth2Config::ConfigType configtype )
773 {
774   switch ( configtype )
775   {
776     case QgsAuthOAuth2Config::Custom:
777       return tr( "Custom" );
778     case QgsAuthOAuth2Config::Predefined:
779     default:
780       return tr( "Predefined" );
781   }
782 }
783 
784 // static
grantFlowString(QgsAuthOAuth2Config::GrantFlow flow)785 QString QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::GrantFlow flow )
786 {
787   switch ( flow )
788   {
789     case QgsAuthOAuth2Config::AuthCode:
790       return tr( "Authorization Code" );
791     case QgsAuthOAuth2Config::Implicit:
792       return tr( "Implicit" );
793     case QgsAuthOAuth2Config::ResourceOwner:
794     default:
795       return tr( "Resource Owner" );
796   }
797 }
798 
799 // static
accessMethodString(QgsAuthOAuth2Config::AccessMethod method)800 QString QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::AccessMethod method )
801 {
802   switch ( method )
803   {
804     case QgsAuthOAuth2Config::Header:
805       return tr( "Header" );
806     case QgsAuthOAuth2Config::Form:
807       return tr( "Form (POST only)" );
808     case QgsAuthOAuth2Config::Query:
809     default:
810       return tr( "URL Query" );
811   }
812 }
813 
814 // static
tokenCacheDirectory(bool temporary)815 QString QgsAuthOAuth2Config::tokenCacheDirectory( bool temporary )
816 {
817   const QDir setdir( QgsApplication::qgisSettingsDirPath() );
818   return  QStringLiteral( "%1/oauth2-cache" ).arg( temporary ? QDir::tempPath() : setdir.canonicalPath() );
819 }
820 
821 // static
tokenCacheFile(const QString & suffix)822 QString QgsAuthOAuth2Config::tokenCacheFile( const QString &suffix )
823 {
824   return QStringLiteral( "authcfg-%1.ini" ).arg( !suffix.isEmpty() ? suffix : QStringLiteral( "cache" ) );
825 }
826 
827 // static
tokenCachePath(const QString & suffix,bool temporary)828 QString QgsAuthOAuth2Config::tokenCachePath( const QString &suffix, bool temporary )
829 {
830   return QStringLiteral( "%1/%2" ).arg( QgsAuthOAuth2Config::tokenCacheDirectory( temporary ),
831                                         QgsAuthOAuth2Config::tokenCacheFile( suffix ) );
832 }
833