1 /***************************************************************************
2 qgsauthconfig.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 "qgsauthconfig.h"
18 #include "qgsauthcertutils.h"
19 #include "qgsxmlutils.h"
20
21 #include <QtCrypto>
22
23 #include <QFile>
24 #include <QObject>
25 #include <QCryptographicHash>
26 #include <QUrl>
27
28
29 //////////////////////////////////////////////
30 // QgsAuthMethodConfig
31 //////////////////////////////////////////////
32
33 const QString QgsAuthMethodConfig::CONFIG_SEP = QStringLiteral( "|||" );
34 const QString QgsAuthMethodConfig::CONFIG_KEY_SEP = QStringLiteral( ":::" );
35 const QString QgsAuthMethodConfig::CONFIG_LIST_SEP = QStringLiteral( "```" );
36
37 const int QgsAuthMethodConfig::CONFIG_VERSION = 1;
38
39 // get uniqueConfigId only on save
QgsAuthMethodConfig(const QString & method,int version)40 QgsAuthMethodConfig::QgsAuthMethodConfig( const QString &method, int version )
41 : mId( QString() )
42 , mName( QString() )
43 , mUri( QString() )
44 , mMethod( method )
45 , mVersion( version )
46 , mConfigMap( QgsStringMap() )
47 {
48 }
49
operator ==(const QgsAuthMethodConfig & other) const50 bool QgsAuthMethodConfig::operator==( const QgsAuthMethodConfig &other ) const
51 {
52 return ( other.id() == id()
53 && other.name() == name()
54 && other.uri() == uri()
55 && other.method() == method()
56 && other.version() == version()
57 && other.configMap() == configMap() );
58 }
59
operator !=(const QgsAuthMethodConfig & other) const60 bool QgsAuthMethodConfig::operator!=( const QgsAuthMethodConfig &other ) const
61 {
62 return !( *this == other );
63 }
64
isValid(bool validateid) const65 bool QgsAuthMethodConfig::isValid( bool validateid ) const
66 {
67 const bool idvalid = validateid ? !mId.isEmpty() : true;
68
69 return (
70 idvalid
71 && !mName.isEmpty()
72 && !mMethod.isEmpty()
73 );
74 }
75
configString() const76 const QString QgsAuthMethodConfig::configString() const
77 {
78 QStringList confstrs;
79 QgsStringMap::const_iterator i = mConfigMap.constBegin();
80 while ( i != mConfigMap.constEnd() )
81 {
82 confstrs << i.key() + CONFIG_KEY_SEP + i.value();
83 ++i;
84 }
85 return confstrs.join( CONFIG_SEP );
86 }
87
loadConfigString(const QString & configstr)88 void QgsAuthMethodConfig::loadConfigString( const QString &configstr )
89 {
90 clearConfigMap();
91 if ( configstr.isEmpty() )
92 {
93 return;
94 }
95
96 const QStringList confs( configstr.split( CONFIG_SEP ) );
97
98 for ( const auto &conf : confs )
99 {
100 if ( conf.contains( CONFIG_KEY_SEP ) )
101 {
102 const QStringList keyval( conf.split( CONFIG_KEY_SEP ) );
103 setConfig( keyval.at( 0 ), keyval.at( 1 ) );
104 }
105 }
106
107 if ( configMap().empty() )
108 {
109 setConfig( QStringLiteral( "oldconfigstyle" ), configstr );
110 }
111 }
112
setConfig(const QString & key,const QString & value)113 void QgsAuthMethodConfig::setConfig( const QString &key, const QString &value )
114 {
115 mConfigMap.insert( key, value );
116 }
117
setConfigList(const QString & key,const QStringList & value)118 void QgsAuthMethodConfig::setConfigList( const QString &key, const QStringList &value )
119 {
120 setConfig( key, value.join( CONFIG_LIST_SEP ) );
121 }
122
removeConfig(const QString & key)123 int QgsAuthMethodConfig::removeConfig( const QString &key )
124 {
125 return mConfigMap.remove( key );
126 }
127
config(const QString & key,const QString & defaultvalue) const128 QString QgsAuthMethodConfig::config( const QString &key, const QString &defaultvalue ) const
129 {
130 return mConfigMap.value( key, defaultvalue );
131 }
132
configList(const QString & key) const133 QStringList QgsAuthMethodConfig::configList( const QString &key ) const
134 {
135 return config( key ).split( CONFIG_LIST_SEP );
136 }
137
hasConfig(const QString & key) const138 bool QgsAuthMethodConfig::hasConfig( const QString &key ) const
139 {
140 return mConfigMap.contains( key );
141 }
142
uriToResource(const QString & accessurl,QString * resource,bool withpath)143 bool QgsAuthMethodConfig::uriToResource( const QString &accessurl, QString *resource, bool withpath )
144 {
145 QString res = QString();
146 if ( !accessurl.isEmpty() )
147 {
148 const QUrl url( accessurl );
149 if ( url.isValid() )
150 {
151 res = QStringLiteral( "%1://%2:%3%4" ).arg( url.scheme(), url.host() )
152 .arg( url.port() ).arg( withpath ? url.path() : QString() );
153 }
154 }
155 *resource = res;
156 return ( !res.isEmpty() );
157 }
158
159
writeXml(QDomElement & parentElement,QDomDocument & document)160 bool QgsAuthMethodConfig::writeXml( QDomElement &parentElement, QDomDocument &document )
161 {
162 QDomElement element = document.createElement( QStringLiteral( "AuthMethodConfig" ) );
163 element.setAttribute( QStringLiteral( "method" ), mMethod );
164 element.setAttribute( QStringLiteral( "id" ), mId );
165 element.setAttribute( QStringLiteral( "name" ), mName );
166 element.setAttribute( QStringLiteral( "version" ), QString::number( mVersion ) );
167 element.setAttribute( QStringLiteral( "uri" ), mUri );
168
169 QDomElement configElements = document.createElement( QStringLiteral( "Config" ) );
170 QgsStringMap::const_iterator i = mConfigMap.constBegin();
171 while ( i != mConfigMap.constEnd() )
172 {
173 configElements.setAttribute( i.key(), i.value() );
174 ++i;
175 }
176 element.appendChild( configElements );
177
178 parentElement.appendChild( element );
179 return true;
180 }
181
readXml(const QDomElement & element)182 bool QgsAuthMethodConfig::readXml( const QDomElement &element )
183 {
184 if ( element.nodeName() != QLatin1String( "AuthMethodConfig" ) )
185 return false;
186
187 mMethod = element.attribute( QStringLiteral( "method" ) );
188 mId = element.attribute( QStringLiteral( "id" ) );
189 mName = element.attribute( QStringLiteral( "name" ) );
190 mVersion = element.attribute( QStringLiteral( "version" ) ).toInt();
191 mUri = element.attribute( QStringLiteral( "uri" ) );
192
193 clearConfigMap();
194 const QDomNamedNodeMap configAttributes = element.firstChildElement().attributes();
195 for ( int i = 0; i < configAttributes.length(); i++ )
196 {
197 const QDomAttr configAttribute = configAttributes.item( i ).toAttr();
198 setConfig( configAttribute.name(), configAttribute.value() );
199 }
200
201 return true;
202 }
203
204 #ifndef QT_NO_SSL
205
206 //////////////////////////////////////////////////////
207 // QgsPkiBundle
208 //////////////////////////////////////////////////////
209
QgsPkiBundle(const QSslCertificate & clientCert,const QSslKey & clientKey,const QList<QSslCertificate> & caChain)210 QgsPkiBundle::QgsPkiBundle( const QSslCertificate &clientCert,
211 const QSslKey &clientKey,
212 const QList<QSslCertificate> &caChain )
213 : mCert( QSslCertificate() )
214 , mCertKey( QSslKey() )
215 , mCaChain( caChain )
216 {
217 setClientCert( clientCert );
218 setClientKey( clientKey );
219 }
220
fromPemPaths(const QString & certPath,const QString & keyPath,const QString & keyPass,const QList<QSslCertificate> & caChain)221 const QgsPkiBundle QgsPkiBundle::fromPemPaths( const QString &certPath,
222 const QString &keyPath,
223 const QString &keyPass,
224 const QList<QSslCertificate> &caChain )
225 {
226 QgsPkiBundle pkibundle;
227 if ( !certPath.isEmpty() && !keyPath.isEmpty()
228 && ( certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive )
229 || certPath.endsWith( QLatin1String( ".der" ), Qt::CaseInsensitive ) )
230 && QFile::exists( certPath ) && QFile::exists( keyPath )
231 )
232 {
233 // client cert
234 const bool pem = certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive );
235 const QSslCertificate clientcert( QgsAuthCertUtils::fileData( certPath ), pem ? QSsl::Pem : QSsl::Der );
236 pkibundle.setClientCert( clientcert );
237
238 QSslKey clientkey;
239 clientkey = QgsAuthCertUtils::keyFromFile( keyPath, keyPass );
240 pkibundle.setClientKey( clientkey );
241 if ( !caChain.isEmpty() )
242 {
243 pkibundle.setCaChain( caChain );
244 }
245 }
246 return pkibundle;
247 }
248
fromPkcs12Paths(const QString & bundlepath,const QString & bundlepass)249 const QgsPkiBundle QgsPkiBundle::fromPkcs12Paths( const QString &bundlepath,
250 const QString &bundlepass )
251 {
252 QgsPkiBundle pkibundle;
253 if ( QCA::isSupported( "pkcs12" )
254 && !bundlepath.isEmpty()
255 && ( bundlepath.endsWith( QLatin1String( ".p12" ), Qt::CaseInsensitive )
256 || bundlepath.endsWith( QLatin1String( ".pfx" ), Qt::CaseInsensitive ) )
257 && QFile::exists( bundlepath ) )
258 {
259 QCA::SecureArray passarray;
260 if ( !bundlepass.isNull() )
261 passarray = QCA::SecureArray( bundlepass.toUtf8() );
262 QCA::ConvertResult res;
263 const QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( bundlepath, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
264 if ( res == QCA::ConvertGood && !bundle.isNull() )
265 {
266 const QCA::CertificateChain cert_chain( bundle.certificateChain() );
267 const QSslCertificate cert( cert_chain.primary().toPEM().toLatin1() );
268 if ( !cert.isNull() )
269 {
270 pkibundle.setClientCert( cert );
271 }
272 const QSslKey cert_key( bundle.privateKey().toPEM().toLatin1(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray() );
273 if ( !cert_key.isNull() )
274 {
275 pkibundle.setClientKey( cert_key );
276 }
277
278 if ( cert_chain.size() > 1 )
279 {
280 QList<QSslCertificate> ca_chain;
281 for ( const auto &ca_cert : cert_chain )
282 {
283 if ( ca_cert != cert_chain.primary() )
284 {
285 ca_chain << QSslCertificate( ca_cert.toPEM().toLatin1() );
286 }
287 }
288 pkibundle.setCaChain( ca_chain );
289 }
290
291 }
292 }
293 return pkibundle;
294 }
295
isNull() const296 bool QgsPkiBundle::isNull() const
297 {
298 return ( mCert.isNull() || mCertKey.isNull() );
299 }
300
isValid() const301 bool QgsPkiBundle::isValid() const
302 {
303 return ( !isNull() && QgsAuthCertUtils::certIsViable( mCert ) );
304 }
305
certId() const306 const QString QgsPkiBundle::certId() const
307 {
308 if ( mCert.isNull() )
309 {
310 return QString();
311 }
312 return QString( mCert.digest( QCryptographicHash::Sha1 ).toHex() );
313 }
314
setClientCert(const QSslCertificate & cert)315 void QgsPkiBundle::setClientCert( const QSslCertificate &cert )
316 {
317 mCert.clear();
318 if ( !cert.isNull() )
319 {
320 mCert = cert;
321 }
322 }
323
setClientKey(const QSslKey & certkey)324 void QgsPkiBundle::setClientKey( const QSslKey &certkey )
325 {
326 mCertKey.clear();
327 if ( !certkey.isNull() && certkey.type() == QSsl::PrivateKey )
328 {
329 mCertKey = certkey;
330 }
331 }
332
333
334 //////////////////////////////////////////////////////
335 // QgsPkiConfigBundle
336 //////////////////////////////////////////////////////
337
QgsPkiConfigBundle(const QgsAuthMethodConfig & config,const QSslCertificate & cert,const QSslKey & certkey,const QList<QSslCertificate> & cachain)338 QgsPkiConfigBundle::QgsPkiConfigBundle( const QgsAuthMethodConfig &config,
339 const QSslCertificate &cert,
340 const QSslKey &certkey,
341 const QList<QSslCertificate> &cachain )
342 : mConfig( config )
343 , mCert( cert )
344 , mCertKey( certkey )
345 , mCaChain( cachain )
346 {
347 }
348
isValid()349 bool QgsPkiConfigBundle::isValid()
350 {
351 return ( !mCert.isNull() && !mCertKey.isNull() );
352 }
353
354
355 //////////////////////////////////////////////
356 // QgsAuthConfigSslServer
357 //////////////////////////////////////////////
358
359 const QString QgsAuthConfigSslServer::CONF_SEP = QStringLiteral( "|||" );
360
QgsAuthConfigSslServer()361 QgsAuthConfigSslServer::QgsAuthConfigSslServer()
362 : mSslHostPort( QString() )
363 , mSslCert( QSslCertificate() )
364 , mSslIgnoredErrors( QList<QSslError::SslError>() )
365 {
366 // TODO: figure out if Qt 5 has changed yet again, e.g. TLS-only
367 mQtVersion = 480;
368 // Qt 4.8 defaults to SecureProtocols, i.e. TlsV1SslV3
369 // http://qt-project.org/doc/qt-4.8/qssl.html#SslProtocol-enum
370 mSslProtocol = QSsl::SecureProtocols;
371 }
372
sslIgnoredErrors() const373 const QList<QSslError> QgsAuthConfigSslServer::sslIgnoredErrors() const
374 {
375 QList<QSslError> errors;
376 const QList<QSslError::SslError> ignoredErrors = sslIgnoredErrorEnums();
377 for ( const QSslError::SslError errenum : ignoredErrors )
378 {
379 errors << QSslError( errenum );
380 }
381 return errors;
382 }
383
configString() const384 const QString QgsAuthConfigSslServer::configString() const
385 {
386 QStringList configlist;
387 configlist << QString::number( mVersion ) << QString::number( mQtVersion );
388
389 configlist << QString::number( static_cast< int >( mSslProtocol ) );
390
391 QStringList errs;
392 for ( const auto err : mSslIgnoredErrors )
393 {
394 errs << QString::number( static_cast< int >( err ) );
395 }
396 configlist << errs.join( QLatin1String( "~~" ) );
397
398 configlist << QStringLiteral( "%1~~%2" ).arg( static_cast< int >( mSslPeerVerifyMode ) ).arg( mSslPeerVerifyDepth );
399
400 return configlist.join( CONF_SEP );
401 }
402
loadConfigString(const QString & config)403 void QgsAuthConfigSslServer::loadConfigString( const QString &config )
404 {
405 if ( config.isEmpty() )
406 {
407 return;
408 }
409 const QStringList configlist( config.split( CONF_SEP ) );
410
411 mVersion = configlist.at( 0 ).toInt();
412 mQtVersion = configlist.at( 1 ).toInt();
413
414 // TODO: Conversion between 4.7 -> 4.8 protocol enum differences (and reverse?).
415 // This is necessary for users upgrading from 4.7 to 4.8
416 mSslProtocol = static_cast< QSsl::SslProtocol >( configlist.at( 2 ).toInt() );
417
418 mSslIgnoredErrors.clear();
419 const QStringList errs( configlist.at( 3 ).split( QStringLiteral( "~~" ) ) );
420 for ( const auto &err : errs )
421 {
422 mSslIgnoredErrors.append( static_cast< QSslError::SslError >( err.toInt() ) );
423 }
424
425 const QStringList peerverify( configlist.at( 4 ).split( QStringLiteral( "~~" ) ) );
426 mSslPeerVerifyMode = static_cast< QSslSocket::PeerVerifyMode >( peerverify.at( 0 ).toInt() );
427 mSslPeerVerifyDepth = peerverify.at( 1 ).toInt();
428 }
429
isNull() const430 bool QgsAuthConfigSslServer::isNull() const
431 {
432 return mSslCert.isNull() && mSslHostPort.isEmpty();
433 }
434
435 #endif
436