1 /***************************************************************************
2     qgsauthmethodregistry.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 "qgsauthmethodregistry.h"
18 
19 #include <QString>
20 #include <QDir>
21 #include <QLibrary>
22 
23 #include "qgis.h"
24 #include "qgsauthconfig.h"
25 #include "qgsauthmethod.h"
26 #include "qgslogger.h"
27 #include "qgsmessageoutput.h"
28 #include "qgsmessagelog.h"
29 #include "qgsauthmethodmetadata.h"
30 
31 
32 // typedefs for auth method plugin functions of interest
33 typedef QString methodkey_t();
34 typedef QString description_t();
35 typedef bool    isauthmethod_t();
36 
37 
instance(const QString & pluginPath)38 QgsAuthMethodRegistry *QgsAuthMethodRegistry::instance( const QString &pluginPath )
39 {
40   static QgsAuthMethodRegistry *sInstance( new QgsAuthMethodRegistry( pluginPath ) );
41   return sInstance;
42 }
43 
QgsAuthMethodRegistry(const QString & pluginPath)44 QgsAuthMethodRegistry::QgsAuthMethodRegistry( const QString &pluginPath )
45 {
46   // At startup, examine the libs in the qgis/lib dir and store those that
47   // are an auth method shared lib
48   // check all libs in the current plugin directory and get name and descriptions
49 #if 0
50   char **argv = qApp->argv();
51   QString appDir = argv[0];
52   int bin = appDir.findRev( "/bin", -1, false );
53   QString baseDir = appDir.left( bin );
54   QString mLibraryDirectory = baseDir + "/lib";
55 #endif
56   mLibraryDirectory.setPath( pluginPath );
57   mLibraryDirectory.setSorting( QDir::Name | QDir::IgnoreCase );
58   mLibraryDirectory.setFilter( QDir::Files | QDir::NoSymLinks );
59 
60 #if defined(Q_OS_WIN) || defined(__CYGWIN__)
61   mLibraryDirectory.setNameFilters( QStringList( "*authmethod.dll" ) );
62 #else
63   mLibraryDirectory.setNameFilters( QStringList( QStringLiteral( "*authmethod.so" ) ) );
64 #endif
65 
66   QgsDebugMsgLevel( QStringLiteral( "Checking for auth method plugins in: %1" ).arg( mLibraryDirectory.path() ), 2 );
67 
68   if ( mLibraryDirectory.count() == 0 )
69   {
70     QString msg = QObject::tr( "No QGIS auth method plugins found in:\n%1\n" ).arg( mLibraryDirectory.path() );
71     msg += QObject::tr( "No authentication methods can be used. Check your QGIS installation" );
72 
73     QgsMessageOutput *output = QgsMessageOutput::createMessageOutput();
74     output->setTitle( QObject::tr( "No Authentication Methods" ) );
75     output->setMessage( msg, QgsMessageOutput::MessageText );
76     output->showMessage();
77     return;
78   }
79 
80   // auth method file regex pattern, only files matching the pattern are loaded if the variable is defined
81   QString filePattern = getenv( "QGIS_AUTHMETHOD_FILE" );
82   QRegExp fileRegexp;
83   if ( !filePattern.isEmpty() )
84   {
85     fileRegexp.setPattern( filePattern );
86   }
87 
88   QListIterator<QFileInfo> it( mLibraryDirectory.entryInfoList() );
89   while ( it.hasNext() )
90   {
91     QFileInfo fi( it.next() );
92 
93     if ( !fileRegexp.isEmpty() )
94     {
95       if ( fileRegexp.indexIn( fi.fileName() ) == -1 )
96       {
97         QgsDebugMsg( "auth method " + fi.fileName() + " skipped because doesn't match pattern " + filePattern );
98         continue;
99       }
100     }
101 
102     QLibrary myLib( fi.filePath() );
103     if ( !myLib.load() )
104     {
105       QgsDebugMsg( QStringLiteral( "Checking %1: ...invalid (lib not loadable): %2" ).arg( myLib.fileName(), myLib.errorString() ) );
106       continue;
107     }
108 
109     // get the description and the key for the auth method plugin
110     isauthmethod_t *isAuthMethod = reinterpret_cast< isauthmethod_t * >( cast_to_fptr( myLib.resolve( "isAuthMethod" ) ) );
111     if ( !isAuthMethod )
112     {
113       QgsDebugMsg( QStringLiteral( "Checking %1: ...invalid (no isAuthMethod method)" ).arg( myLib.fileName() ) );
114       continue;
115     }
116 
117     // check to see if this is an auth method plugin
118     if ( !isAuthMethod() )
119     {
120       QgsDebugMsg( QStringLiteral( "Checking %1: ...invalid (not an auth method)" ).arg( myLib.fileName() ) );
121       continue;
122     }
123 
124     // looks like an auth method plugin. get the key and description
125     description_t *pDesc = reinterpret_cast< description_t * >( cast_to_fptr( myLib.resolve( "description" ) ) );
126     if ( !pDesc )
127     {
128       QgsDebugMsg( QStringLiteral( "Checking %1: ...invalid (no description method)" ).arg( myLib.fileName() ) );
129       continue;
130     }
131 
132     methodkey_t *pKey = reinterpret_cast< methodkey_t * >( cast_to_fptr( myLib.resolve( "authMethodKey" ) ) );
133     if ( !pKey )
134     {
135       QgsDebugMsg( QStringLiteral( "Checking %1: ...invalid (no authMethodKey method)" ).arg( myLib.fileName() ) );
136       continue;
137     }
138 
139     // add this auth method to the method map
140     mAuthMethods[pKey()] = new QgsAuthMethodMetadata( pKey(), pDesc(), myLib.fileName() );
141 
142   }
143 }
144 
145 // typedef for the unload auth method function
146 typedef void cleanupAuthMethod_t();
147 
~QgsAuthMethodRegistry()148 QgsAuthMethodRegistry::~QgsAuthMethodRegistry()
149 {
150   AuthMethods::const_iterator it = mAuthMethods.begin();
151 
152   while ( it != mAuthMethods.end() )
153   {
154     QgsDebugMsgLevel( QStringLiteral( "cleanup: %1" ).arg( it->first ), 5 );
155     QString lib = it->second->library();
156     QLibrary myLib( lib );
157     if ( myLib.isLoaded() )
158     {
159       cleanupAuthMethod_t *cleanupFunc = reinterpret_cast< cleanupAuthMethod_t * >( cast_to_fptr( myLib.resolve( "cleanupAuthMethod" ) ) );
160       if ( cleanupFunc )
161         cleanupFunc();
162     }
163     // clear cached QgsAuthMethodMetadata *
164     delete it->second;
165     ++it;
166   }
167 }
168 
169 
170 /**
171  * Convenience function for finding any existing auth methods that match "authMethodKey"
172 
173   Necessary because [] map operator will create a QgsProviderMetadata
174   instance.  Also you cannot use the map [] operator in const members for that
175   very reason.  So there needs to be a convenient way to find an auth method
176   without accidentally adding a null meta data item to the metadata map.
177 */
findMetadata_(QgsAuthMethodRegistry::AuthMethods const & metaData,QString const & authMethodKey)178 static QgsAuthMethodMetadata *findMetadata_( QgsAuthMethodRegistry::AuthMethods const &metaData,
179     QString const &authMethodKey )
180 {
181   QgsAuthMethodRegistry::AuthMethods::const_iterator i =
182     metaData.find( authMethodKey );
183 
184   if ( i != metaData.end() )
185   {
186     return i->second;
187   }
188 
189   return nullptr;
190 } // findMetadata_
191 
192 
library(const QString & authMethodKey) const193 QString QgsAuthMethodRegistry::library( const QString &authMethodKey ) const
194 {
195   QgsAuthMethodMetadata *md = findMetadata_( mAuthMethods, authMethodKey );
196 
197   if ( md )
198   {
199     return md->library();
200   }
201 
202   return QString();
203 }
204 
pluginList(bool asHtml) const205 QString QgsAuthMethodRegistry::pluginList( bool asHtml ) const
206 {
207   AuthMethods::const_iterator it = mAuthMethods.begin();
208 
209   if ( mAuthMethods.empty() )
210   {
211     return QObject::tr( "No authentication method plugins are available." );
212   }
213 
214   QString list;
215 
216   if ( asHtml )
217   {
218     list += QLatin1String( "<ol>" );
219   }
220 
221   while ( it != mAuthMethods.end() )
222   {
223     if ( asHtml )
224     {
225       list += QLatin1String( "<li>" );
226     }
227 
228     list += it->second->description();
229 
230     if ( asHtml )
231     {
232       list += QLatin1String( "<br></li>" );
233     }
234     else
235     {
236       list += '\n';
237     }
238 
239     ++it;
240   }
241 
242   if ( asHtml )
243   {
244     list += QLatin1String( "</ol>" );
245   }
246 
247   return list;
248 }
249 
libraryDirectory() const250 QDir QgsAuthMethodRegistry::libraryDirectory() const
251 {
252   return mLibraryDirectory;
253 }
254 
setLibraryDirectory(const QDir & path)255 void QgsAuthMethodRegistry::setLibraryDirectory( const QDir &path )
256 {
257   mLibraryDirectory = path;
258 }
259 
260 
261 // typedef for the QgsDataProvider class factory
262 typedef QgsAuthMethod *classFactoryFunction_t();
263 
authMethod(const QString & authMethodKey)264 std::unique_ptr<QgsAuthMethod> QgsAuthMethodRegistry::authMethod( const QString &authMethodKey )
265 {
266   // load the plugin
267   QString lib = library( authMethodKey );
268 
269 #ifdef TESTAUTHMETHODLIB
270   const char *cLib = lib.toUtf8();
271 
272   // test code to help debug auth method plugin loading problems
273   //  void *handle = dlopen(cLib, RTLD_LAZY);
274   void *handle = dlopen( cOgrLib, RTLD_LAZY | RTLD_GLOBAL );
275   if ( !handle )
276   {
277     QgsLogger::warning( "Error in dlopen" );
278   }
279   else
280   {
281     QgsDebugMsg( QStringLiteral( "dlopen succeeded" ) );
282     dlclose( handle );
283   }
284 
285 #endif
286   // load the auth method
287   QLibrary myLib( lib );
288 
289   QgsDebugMsgLevel( "Auth method library name is " + myLib.fileName(), 2 );
290   if ( !myLib.load() )
291   {
292     QgsMessageLog::logMessage( QObject::tr( "Failed to load %1: %2" ).arg( lib, myLib.errorString() ) );
293     return nullptr;
294   }
295 
296   classFactoryFunction_t *classFactory = reinterpret_cast< classFactoryFunction_t * >( cast_to_fptr( myLib.resolve( "classFactory" ) ) );
297   if ( !classFactory )
298   {
299     QgsDebugMsg( QStringLiteral( "Failed to load %1: no classFactory method" ).arg( lib ) );
300     return nullptr;
301   }
302 
303   std::unique_ptr< QgsAuthMethod > authMethod( classFactory() );
304   if ( !authMethod )
305   {
306     QgsMessageLog::logMessage( QObject::tr( "Unable to instantiate the auth method plugin %1" ).arg( lib ) );
307     myLib.unload();
308     return nullptr;
309   }
310 
311   QgsDebugMsgLevel( QStringLiteral( "Instantiated the auth method plugin: %1" ).arg( authMethod->key() ), 2 );
312   return authMethod;
313 }
314 
315 typedef QWidget *editFactoryFunction_t( QWidget *parent );
316 
editWidget(const QString & authMethodKey,QWidget * parent)317 QWidget *QgsAuthMethodRegistry::editWidget( const QString &authMethodKey, QWidget *parent )
318 {
319   editFactoryFunction_t *editFactory =
320     reinterpret_cast< editFactoryFunction_t * >( cast_to_fptr( function( authMethodKey, QStringLiteral( "editWidget" ) ) ) );
321 
322   if ( !editFactory )
323     return nullptr;
324 
325   return editFactory( parent );
326 }
327 
function(QString const & authMethodKey,QString const & functionName)328 QFunctionPointer QgsAuthMethodRegistry::function( QString const &authMethodKey,
329     QString const &functionName )
330 {
331   QLibrary myLib( library( authMethodKey ) );
332 
333   QgsDebugMsg( "Library name is " + myLib.fileName() );
334 
335   if ( myLib.load() )
336   {
337     return myLib.resolve( functionName.toLatin1().data() );
338   }
339   else
340   {
341     QgsDebugMsg( "Cannot load library: " + myLib.errorString() );
342     return nullptr;
343   }
344 }
345 
authMethodLibrary(const QString & authMethodKey) const346 std::unique_ptr<QLibrary> QgsAuthMethodRegistry::authMethodLibrary( const QString &authMethodKey ) const
347 {
348   std::unique_ptr< QLibrary > myLib( new QLibrary( library( authMethodKey ) ) );
349 
350   QgsDebugMsg( "Library name is " + myLib->fileName() );
351 
352   if ( myLib->load() )
353     return myLib;
354 
355   QgsDebugMsg( "Cannot load library: " + myLib->errorString() );
356   return nullptr;
357 }
358 
authMethodList() const359 QStringList QgsAuthMethodRegistry::authMethodList() const
360 {
361   QStringList lst;
362   for ( AuthMethods::const_iterator it = mAuthMethods.begin(); it != mAuthMethods.end(); ++it )
363   {
364     lst.append( it->first );
365   }
366   return lst;
367 }
368 
authMethodMetadata(const QString & authMethodKey) const369 const QgsAuthMethodMetadata *QgsAuthMethodRegistry::authMethodMetadata( const QString &authMethodKey ) const
370 {
371   return findMetadata_( mAuthMethods, authMethodKey );
372 }
373