2017-11-25 22:19:25 +03:00
# include "webflowcredentials.h"
2017-11-28 22:06:27 +03:00
# include "creds/httpcredentials.h"
# include <QAuthenticator>
# include <QNetworkAccessManager>
# include <QNetworkReply>
# include <QPointer>
# include <QTimer>
2017-11-25 22:19:25 +03:00
# include <keychain.h>
2017-11-29 00:25:35 +03:00
# include <QDialog>
# include <QVBoxLayout>
# include <QLabel>
2017-11-25 22:19:25 +03:00
# include "accessmanager.h"
# include "account.h"
2019-08-27 04:32:21 +03:00
# include "configfile.h"
2017-11-25 22:19:25 +03:00
# include "theme.h"
2017-11-29 00:25:35 +03:00
# include "wizard/webview.h"
# include "webflowcredentialsdialog.h"
2019-12-24 09:12:54 +03:00
# include "keychainchunk.h"
2017-11-25 22:19:25 +03:00
using namespace QKeychain ;
namespace OCC {
2017-11-28 22:06:27 +03:00
Q_LOGGING_CATEGORY ( lcWebFlowCredentials , " sync.credentials.webflow " , QtInfoMsg )
2019-08-27 04:32:21 +03:00
namespace {
const char userC [ ] = " user " ;
const char clientCertificatePEMC [ ] = " _clientCertificatePEM " ;
const char clientKeyPEMC [ ] = " _clientKeyPEM " ;
2019-08-30 05:56:01 +03:00
const char clientCaCertificatePEMC [ ] = " _clientCaCertificatePEM " ;
2019-08-27 04:32:21 +03:00
} // ns
2018-09-04 21:59:25 +03:00
class WebFlowCredentialsAccessManager : public AccessManager
{
public :
WebFlowCredentialsAccessManager ( const WebFlowCredentials * cred , QObject * parent = nullptr )
: AccessManager ( parent )
, _cred ( cred )
{
}
protected :
2018-11-11 13:09:29 +03:00
QNetworkReply * createRequest ( Operation op , const QNetworkRequest & request , QIODevice * outgoingData ) override
2018-09-04 21:59:25 +03:00
{
QNetworkRequest req ( request ) ;
2019-08-27 04:32:21 +03:00
if ( ! req . attribute ( WebFlowCredentials : : DontAddCredentialsAttribute ) . toBool ( ) ) {
2018-09-04 21:59:25 +03:00
if ( _cred & & ! _cred - > password ( ) . isEmpty ( ) ) {
QByteArray credHash = QByteArray ( _cred - > user ( ) . toUtf8 ( ) + " : " + _cred - > password ( ) . toUtf8 ( ) ) . toBase64 ( ) ;
req . setRawHeader ( " Authorization " , " Basic " + credHash ) ;
}
}
2019-08-27 04:32:21 +03:00
if ( _cred & & ! _cred - > _clientSslKey . isNull ( ) & & ! _cred - > _clientSslCertificate . isNull ( ) ) {
// SSL configuration
QSslConfiguration sslConfiguration = req . sslConfiguration ( ) ;
sslConfiguration . setLocalCertificate ( _cred - > _clientSslCertificate ) ;
sslConfiguration . setPrivateKey ( _cred - > _clientSslKey ) ;
2019-08-27 10:55:41 +03:00
// Merge client side CA with system CA
auto ca = sslConfiguration . systemCaCertificates ( ) ;
ca . append ( _cred - > _clientSslCaCertificates ) ;
sslConfiguration . setCaCertificates ( ca ) ;
2019-08-27 04:32:21 +03:00
req . setSslConfiguration ( sslConfiguration ) ;
}
2018-09-04 21:59:25 +03:00
return AccessManager : : createRequest ( op , req , outgoingData ) ;
}
private :
// The credentials object dies along with the account, while the QNAM might
// outlive both.
QPointer < const WebFlowCredentials > _cred ;
} ;
2019-12-24 09:12:54 +03:00
# if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
2019-08-27 04:32:21 +03:00
static void addSettingsToJob ( Account * account , QKeychain : : Job * job )
{
2019-08-27 10:55:41 +03:00
Q_UNUSED ( account )
2019-08-27 04:32:21 +03:00
auto settings = ConfigFile : : settingsWithGroup ( Theme : : instance ( ) - > appName ( ) ) ;
settings - > setParent ( job ) ; // make the job parent to make setting deleted properly
job - > setSettings ( settings . release ( ) ) ;
}
2019-12-24 09:12:54 +03:00
# endif
2019-08-27 04:32:21 +03:00
2017-11-25 22:19:25 +03:00
WebFlowCredentials : : WebFlowCredentials ( )
{
}
2019-08-27 10:55:41 +03:00
WebFlowCredentials : : WebFlowCredentials ( const QString & user , const QString & password , const QSslCertificate & certificate , const QSslKey & key , const QList < QSslCertificate > & caCertificates )
2017-11-25 22:19:25 +03:00
: _user ( user )
, _password ( password )
, _clientSslKey ( key )
, _clientSslCertificate ( certificate )
2019-08-27 10:55:41 +03:00
, _clientSslCaCertificates ( caCertificates )
2017-11-25 22:19:25 +03:00
, _ready ( true )
2017-11-28 22:06:27 +03:00
, _credentialsValid ( true )
2017-11-25 22:19:25 +03:00
{
}
QString WebFlowCredentials : : authType ( ) const {
return QString : : fromLatin1 ( " webflow " ) ;
}
QString WebFlowCredentials : : user ( ) const {
return _user ;
}
2017-11-28 22:06:27 +03:00
QString WebFlowCredentials : : password ( ) const {
return _password ;
}
2017-11-25 22:19:25 +03:00
QNetworkAccessManager * WebFlowCredentials : : createQNAM ( ) const {
2017-11-28 22:06:27 +03:00
qCInfo ( lcWebFlowCredentials ( ) ) < < " Get QNAM " ;
2018-09-04 21:59:25 +03:00
AccessManager * qnam = new WebFlowCredentialsAccessManager ( this ) ;
2017-11-28 22:06:27 +03:00
connect ( qnam , & AccessManager : : authenticationRequired , this , & WebFlowCredentials : : slotAuthentication ) ;
connect ( qnam , & AccessManager : : finished , this , & WebFlowCredentials : : slotFinished ) ;
2017-11-25 22:19:25 +03:00
return qnam ;
}
bool WebFlowCredentials : : ready ( ) const {
2017-11-28 22:06:27 +03:00
return _ready ;
2017-11-25 22:19:25 +03:00
}
void WebFlowCredentials : : fetchFromKeychain ( ) {
2017-11-28 22:06:27 +03:00
_wasFetched = true ;
2019-08-23 11:25:34 +03:00
// Make sure we get the user from the config file
2017-11-28 22:06:27 +03:00
fetchUser ( ) ;
2017-11-25 22:19:25 +03:00
2017-11-28 22:06:27 +03:00
if ( ready ( ) ) {
emit fetched ( ) ;
} else {
2019-04-28 12:03:38 +03:00
qCInfo ( lcWebFlowCredentials ( ) ) < < " Fetch from keychain! " ;
2017-11-28 22:06:27 +03:00
fetchFromKeychainHelper ( ) ;
}
2017-11-25 22:19:25 +03:00
}
2017-11-28 22:06:27 +03:00
void WebFlowCredentials : : askFromUser ( ) {
2019-12-06 22:14:18 +03:00
// Determine if the old flow has to be used (GS for now)
// Do a DetermineAuthTypeJob to make sure that the server is still using Flow2
2020-05-18 21:54:23 +03:00
auto * job = new DetermineAuthTypeJob ( _account - > sharedFromThis ( ) , this ) ;
2019-12-06 22:14:18 +03:00
connect ( job , & DetermineAuthTypeJob : : authType , [ this ] ( DetermineAuthTypeJob : : AuthType type ) {
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
bool useFlow2 = ( type ! = DetermineAuthTypeJob : : WebViewFlow ) ;
2017-11-29 00:25:35 +03:00
2019-12-06 22:14:18 +03:00
_askDialog = new WebFlowCredentialsDialog ( _account , useFlow2 ) ;
2019-08-24 17:21:44 +03:00
2019-12-06 22:14:18 +03:00
if ( ! useFlow2 ) {
QUrl url = _account - > url ( ) ;
QString path = url . path ( ) + " /index.php/login/flow " ;
url . setPath ( path ) ;
_askDialog - > setUrl ( url ) ;
}
2017-11-29 00:25:35 +03:00
2019-12-06 22:14:18 +03:00
QString msg = tr ( " You have been logged out of %1 as user %2. Please login again " )
. arg ( _account - > displayName ( ) , _user ) ;
_askDialog - > setInfo ( msg ) ;
2017-11-29 00:25:35 +03:00
2019-12-06 22:14:18 +03:00
_askDialog - > show ( ) ;
2017-11-29 00:25:35 +03:00
2019-12-06 22:14:18 +03:00
connect ( _askDialog , & WebFlowCredentialsDialog : : urlCatched , this , & WebFlowCredentials : : slotAskFromUserCredentialsProvided ) ;
2019-12-23 07:14:21 +03:00
connect ( _askDialog , & WebFlowCredentialsDialog : : onClose , this , & WebFlowCredentials : : slotAskFromUserCancelled ) ;
2019-12-06 22:14:18 +03:00
} ) ;
job - > start ( ) ;
2017-11-29 00:25:35 +03:00
2019-02-15 22:23:24 +03:00
qCDebug ( lcWebFlowCredentials ( ) ) < < " User needs to reauth! " ;
2017-11-25 22:19:25 +03:00
}
2017-11-29 00:25:35 +03:00
void WebFlowCredentials : : slotAskFromUserCredentialsProvided ( const QString & user , const QString & pass , const QString & host ) {
2019-08-27 10:55:41 +03:00
Q_UNUSED ( host )
2018-07-02 14:02:15 +03:00
2020-03-03 08:01:27 +03:00
// Compare the re-entered username case-insensitive and save the new value (avoid breaking the account)
// See issue: https://github.com/nextcloud/desktop/issues/1741
if ( QString : : compare ( _user , user , Qt : : CaseInsensitive ) = = 0 ) {
_user = user ;
} else {
2017-11-29 00:25:35 +03:00
qCInfo ( lcWebFlowCredentials ( ) ) < < " Authed with the wrong user! " ;
QString msg = tr ( " Please login with the user: %1 " )
2019-08-30 06:35:36 +03:00
. arg ( _user ) ;
2017-11-29 00:25:35 +03:00
_askDialog - > setError ( msg ) ;
2019-08-24 17:21:44 +03:00
if ( ! _askDialog - > isUsingFlow2 ( ) ) {
QUrl url = _account - > url ( ) ;
QString path = url . path ( ) + " /index.php/login/flow " ;
url . setPath ( path ) ;
_askDialog - > setUrl ( url ) ;
}
2017-11-29 00:25:35 +03:00
return ;
}
2018-04-23 22:28:03 +03:00
qCInfo ( lcWebFlowCredentials ( ) ) < < " Obtained a new password " ;
2017-11-29 00:25:35 +03:00
_password = pass ;
_ready = true ;
_credentialsValid = true ;
persist ( ) ;
emit asked ( ) ;
_askDialog - > close ( ) ;
2019-12-23 07:14:21 +03:00
_askDialog - > deleteLater ( ) ;
2018-11-11 12:56:22 +03:00
_askDialog = nullptr ;
2017-11-29 00:25:35 +03:00
}
2019-12-23 07:14:21 +03:00
void WebFlowCredentials : : slotAskFromUserCancelled ( ) {
qCDebug ( lcWebFlowCredentials ( ) ) < < " User cancelled reauth! " ;
emit asked ( ) ;
_askDialog - > deleteLater ( ) ;
_askDialog = nullptr ;
}
2017-11-29 00:25:35 +03:00
2017-11-25 22:19:25 +03:00
bool WebFlowCredentials : : stillValid ( QNetworkReply * reply ) {
2019-02-15 22:23:24 +03:00
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
qCWarning ( lcWebFlowCredentials ( ) ) < < reply - > error ( ) ;
qCWarning ( lcWebFlowCredentials ( ) ) < < reply - > errorString ( ) ;
}
2017-11-28 22:06:27 +03:00
return ( reply - > error ( ) ! = QNetworkReply : : AuthenticationRequiredError ) ;
2017-11-25 22:19:25 +03:00
}
void WebFlowCredentials : : persist ( ) {
2017-11-28 22:06:27 +03:00
if ( _user . isEmpty ( ) ) {
// We don't even have a user nothing to see here move along
return ;
}
2019-08-27 04:32:21 +03:00
_account - > setCredentialSetting ( userC , _user ) ;
2017-11-28 22:06:27 +03:00
_account - > wantsAccountSaved ( _account ) ;
2019-08-27 04:32:21 +03:00
// write cert if there is one
if ( ! _clientSslCertificate . isNull ( ) ) {
2019-12-24 09:12:54 +03:00
auto * job = new KeychainChunk : : WriteJob ( _account ,
_user + clientCertificatePEMC ,
_clientSslCertificate . toPem ( ) ) ;
connect ( job , & KeychainChunk : : WriteJob : : finished , this , & WebFlowCredentials : : slotWriteClientCertPEMJobDone ) ;
2019-08-27 04:32:21 +03:00
job - > start ( ) ;
} else {
// no cert, just write credentials
2019-12-24 09:12:54 +03:00
slotWriteClientCertPEMJobDone ( nullptr ) ;
2019-08-27 04:32:21 +03:00
}
}
2019-12-24 09:12:54 +03:00
void WebFlowCredentials : : slotWriteClientCertPEMJobDone ( KeychainChunk : : WriteJob * writeJob )
2019-08-27 04:32:21 +03:00
{
2019-12-24 09:12:54 +03:00
if ( writeJob )
writeJob - > deleteLater ( ) ;
2019-08-27 04:32:21 +03:00
// write ssl key if there is one
if ( ! _clientSslKey . isNull ( ) ) {
2019-12-24 09:12:54 +03:00
auto * job = new KeychainChunk : : WriteJob ( _account ,
_user + clientKeyPEMC ,
_clientSslKey . toPem ( ) ) ;
connect ( job , & KeychainChunk : : WriteJob : : finished , this , & WebFlowCredentials : : slotWriteClientKeyPEMJobDone ) ;
2019-08-27 04:32:21 +03:00
job - > start ( ) ;
} else {
2019-12-24 09:12:54 +03:00
// no key, just write credentials
slotWriteClientKeyPEMJobDone ( nullptr ) ;
2019-08-27 04:32:21 +03:00
}
}
2019-08-30 05:56:01 +03:00
void WebFlowCredentials : : writeSingleClientCaCertPEM ( )
2019-08-27 10:55:41 +03:00
{
2019-08-30 05:56:01 +03:00
// write a ca cert if there is any in the queue
if ( ! _clientSslCaCertificatesWriteQueue . isEmpty ( ) ) {
// grab and remove the first cert from the queue
auto cert = _clientSslCaCertificatesWriteQueue . dequeue ( ) ;
auto index = ( _clientSslCaCertificates . count ( ) - _clientSslCaCertificatesWriteQueue . count ( ) ) - 1 ;
// keep the limit
if ( index > ( _clientSslCaCertificatesMaxCount - 1 ) ) {
2019-12-08 01:27:57 +03:00
qCWarning ( lcWebFlowCredentials ) < < " Maximum client CA cert count exceeded while writing slot " < < QString : : number ( index ) < < " cutting off after " < < QString : : number ( _clientSslCaCertificatesMaxCount ) < < " certs " ;
2019-08-30 05:56:01 +03:00
_clientSslCaCertificatesWriteQueue . clear ( ) ;
slotWriteClientCaCertsPEMJobDone ( nullptr ) ;
return ;
}
2019-12-24 09:12:54 +03:00
auto * job = new KeychainChunk : : WriteJob ( _account ,
_user + clientCaCertificatePEMC + QString : : number ( index ) ,
cert . toPem ( ) ) ;
connect ( job , & KeychainChunk : : WriteJob : : finished , this , & WebFlowCredentials : : slotWriteClientCaCertsPEMJobDone ) ;
2019-08-30 05:56:01 +03:00
job - > start ( ) ;
} else {
slotWriteClientCaCertsPEMJobDone ( nullptr ) ;
}
}
2019-08-27 10:55:41 +03:00
2019-12-24 09:12:54 +03:00
void WebFlowCredentials : : slotWriteClientKeyPEMJobDone ( KeychainChunk : : WriteJob * writeJob )
2019-08-30 05:56:01 +03:00
{
2019-12-24 09:12:54 +03:00
if ( writeJob )
writeJob - > deleteLater ( ) ;
2019-08-30 05:56:01 +03:00
_clientSslCaCertificatesWriteQueue . clear ( ) ;
2019-08-27 10:55:41 +03:00
2019-08-30 05:56:01 +03:00
// write ca certs if there are any
if ( ! _clientSslCaCertificates . isEmpty ( ) ) {
// queue the certs to avoid trouble on Windows (Workaround for CredWriteW used by QtKeychain)
_clientSslCaCertificatesWriteQueue . append ( _clientSslCaCertificates ) ;
// first ca cert
writeSingleClientCaCertPEM ( ) ;
2019-08-27 10:55:41 +03:00
} else {
2019-08-30 05:56:01 +03:00
slotWriteClientCaCertsPEMJobDone ( nullptr ) ;
2019-08-27 10:55:41 +03:00
}
}
2019-12-24 09:12:54 +03:00
void WebFlowCredentials : : slotWriteClientCaCertsPEMJobDone ( KeychainChunk : : WriteJob * writeJob )
2019-08-27 04:32:21 +03:00
{
2019-08-30 05:56:01 +03:00
// errors / next ca cert?
2019-12-24 09:12:54 +03:00
if ( writeJob & & ! _clientSslCaCertificates . isEmpty ( ) ) {
2019-08-30 05:56:01 +03:00
if ( writeJob - > error ( ) ! = NoError ) {
qCWarning ( lcWebFlowCredentials ) < < " Error while writing client CA cert " < < writeJob - > errorString ( ) ;
}
2019-12-24 09:12:54 +03:00
writeJob - > deleteLater ( ) ;
2019-08-30 05:56:01 +03:00
if ( ! _clientSslCaCertificatesWriteQueue . isEmpty ( ) ) {
// next ca cert
writeSingleClientCaCertPEM ( ) ;
return ;
}
}
// done storing ca certs, time for the password
2020-05-18 21:54:23 +03:00
auto * job = new WritePasswordJob ( Theme : : instance ( ) - > appName ( ) ) ;
2019-12-24 09:12:54 +03:00
# if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
2019-08-27 04:32:21 +03:00
addSettingsToJob ( _account , job ) ;
2019-12-24 09:12:54 +03:00
# endif
2017-11-25 22:19:25 +03:00
job - > setInsecureFallback ( false ) ;
2019-08-27 04:32:21 +03:00
connect ( job , & Job : : finished , this , & WebFlowCredentials : : slotWriteJobDone ) ;
2017-11-25 22:19:25 +03:00
job - > setKey ( keychainKey ( _account - > url ( ) . toString ( ) , _user , _account - > id ( ) ) ) ;
job - > setTextData ( _password ) ;
job - > start ( ) ;
}
2019-08-27 04:32:21 +03:00
void WebFlowCredentials : : slotWriteJobDone ( QKeychain : : Job * job )
{
delete job - > settings ( ) ;
switch ( job - > error ( ) ) {
case NoError :
break ;
default :
qCWarning ( lcWebFlowCredentials ) < < " Error while writing password " < < job - > errorString ( ) ;
}
2020-05-18 21:54:23 +03:00
auto * wjob = qobject_cast < WritePasswordJob * > ( job ) ;
2019-08-27 04:32:21 +03:00
wjob - > deleteLater ( ) ;
}
2017-11-25 22:19:25 +03:00
void WebFlowCredentials : : invalidateToken ( ) {
2017-11-28 22:06:27 +03:00
// clear the session cookie.
_account - > clearCookieJar ( ) ;
// let QNAM forget about the password
// This needs to be done later in the event loop because we might be called (directly or
// indirectly) from QNetworkAccessManagerPrivate::authenticationRequired, which itself
// is a called from a BlockingQueuedConnection from the Qt HTTP thread. And clearing the
// cache needs to synchronize again with the HTTP thread.
QTimer : : singleShot ( 0 , _account , & Account : : clearQNAMCache ) ;
2017-11-25 22:19:25 +03:00
}
2019-08-30 05:56:01 +03:00
void WebFlowCredentials : : forgetSensitiveData ( ) {
2017-11-28 22:06:27 +03:00
_password = QString ( ) ;
_ready = false ;
fetchUser ( ) ;
2019-07-24 14:56:21 +03:00
_account - > deleteAppPassword ( ) ;
2017-11-28 22:06:27 +03:00
const QString kck = keychainKey ( _account - > url ( ) . toString ( ) , _user , _account - > id ( ) ) ;
if ( kck . isEmpty ( ) ) {
2019-02-15 22:23:24 +03:00
qCDebug ( lcWebFlowCredentials ( ) ) < < " InvalidateToken: User is empty, bailing out! " ;
2017-11-28 22:06:27 +03:00
return ;
}
2020-05-18 21:54:23 +03:00
auto * job = new DeletePasswordJob ( Theme : : instance ( ) - > appName ( ) ) ;
2017-11-28 22:06:27 +03:00
job - > setInsecureFallback ( false ) ;
job - > setKey ( kck ) ;
2019-12-24 09:12:54 +03:00
connect ( job , & Job : : finished , this , [ ] ( QKeychain : : Job * job ) {
2020-05-18 21:54:23 +03:00
auto * djob = qobject_cast < DeletePasswordJob * > ( job ) ;
2019-12-24 09:12:54 +03:00
djob - > deleteLater ( ) ;
} ) ;
2017-11-28 22:06:27 +03:00
job - > start ( ) ;
invalidateToken ( ) ;
2019-08-30 05:56:01 +03:00
2019-12-08 02:00:02 +03:00
deleteKeychainEntries ( ) ;
2017-11-28 22:06:27 +03:00
}
void WebFlowCredentials : : setAccount ( Account * account ) {
AbstractCredentials : : setAccount ( account ) ;
if ( _user . isEmpty ( ) ) {
fetchUser ( ) ;
}
}
QString WebFlowCredentials : : fetchUser ( ) {
2019-08-27 04:32:21 +03:00
_user = _account - > credentialSetting ( userC ) . toString ( ) ;
2017-11-28 22:06:27 +03:00
return _user ;
}
void WebFlowCredentials : : slotAuthentication ( QNetworkReply * reply , QAuthenticator * authenticator ) {
2019-08-27 10:55:41 +03:00
Q_UNUSED ( reply )
2017-11-28 22:06:27 +03:00
if ( ! _ready ) {
return ;
}
if ( _credentialsValid = = false ) {
return ;
}
2019-02-15 22:23:24 +03:00
qCDebug ( lcWebFlowCredentials ( ) ) < < " Requires authentication " ;
2017-11-28 22:06:27 +03:00
authenticator - > setUser ( _user ) ;
authenticator - > setPassword ( _password ) ;
_credentialsValid = false ;
}
void WebFlowCredentials : : slotFinished ( QNetworkReply * reply ) {
qCInfo ( lcWebFlowCredentials ( ) ) < < " request finished " ;
2018-05-01 23:07:16 +03:00
if ( reply - > error ( ) = = QNetworkReply : : NoError ) {
_credentialsValid = true ;
2019-07-24 14:56:21 +03:00
/// Used later for remote wipe
2019-11-29 06:28:50 +03:00
_account - > writeAppPasswordOnce ( _password ) ;
2018-05-01 23:07:16 +03:00
}
2017-11-28 22:06:27 +03:00
}
void WebFlowCredentials : : fetchFromKeychainHelper ( ) {
2019-08-27 04:32:21 +03:00
// Read client cert from keychain
2019-12-24 09:12:54 +03:00
auto * job = new KeychainChunk : : ReadJob ( _account ,
_user + clientCertificatePEMC ,
_keychainMigration ) ;
connect ( job , & KeychainChunk : : ReadJob : : finished , this , & WebFlowCredentials : : slotReadClientCertPEMJobDone ) ;
2019-08-27 04:32:21 +03:00
job - > start ( ) ;
}
2019-12-24 09:12:54 +03:00
void WebFlowCredentials : : slotReadClientCertPEMJobDone ( KeychainChunk : : ReadJob * readJob )
2019-08-27 04:32:21 +03:00
{
# if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
2019-12-24 09:12:54 +03:00
Q_ASSERT ( ! readJob - > insecureFallback ( ) ) ; // If insecureFallback is set, the next test would be pointless
if ( _retryOnKeyChainError & & ( readJob - > error ( ) = = QKeychain : : NoBackendAvailable
| | readJob - > error ( ) = = QKeychain : : OtherError ) ) {
2019-08-27 04:32:21 +03:00
// Could be that the backend was not yet available. Wait some extra seconds.
// (Issues #4274 and #6522)
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
2019-12-24 09:12:54 +03:00
qCInfo ( lcWebFlowCredentials ) < < " Backend unavailable (yet?) Retrying in a few seconds. " < < readJob - > errorString ( ) ;
2019-08-27 04:32:21 +03:00
QTimer : : singleShot ( 10000 , this , & WebFlowCredentials : : fetchFromKeychainHelper ) ;
_retryOnKeyChainError = false ;
return ;
}
_retryOnKeyChainError = false ;
# endif
// Store PEM in memory
if ( readJob - > error ( ) = = NoError & & readJob - > binaryData ( ) . length ( ) > 0 ) {
QList < QSslCertificate > sslCertificateList = QSslCertificate : : fromData ( readJob - > binaryData ( ) , QSsl : : Pem ) ;
if ( sslCertificateList . length ( ) > = 1 ) {
_clientSslCertificate = sslCertificateList . at ( 0 ) ;
}
}
2019-12-24 09:12:54 +03:00
readJob - > deleteLater ( ) ;
2019-12-07 07:27:50 +03:00
2019-12-24 09:12:54 +03:00
// Load key too
auto * job = new KeychainChunk : : ReadJob ( _account ,
_user + clientKeyPEMC ,
_keychainMigration ) ;
connect ( job , & KeychainChunk : : ReadJob : : finished , this , & WebFlowCredentials : : slotReadClientKeyPEMJobDone ) ;
2019-08-27 04:32:21 +03:00
job - > start ( ) ;
}
2019-12-24 09:12:54 +03:00
void WebFlowCredentials : : slotReadClientKeyPEMJobDone ( KeychainChunk : : ReadJob * readJob )
2019-08-27 04:32:21 +03:00
{
2019-12-07 07:27:50 +03:00
// Store key in memory
2019-12-24 09:12:54 +03:00
if ( readJob - > error ( ) = = NoError & & readJob - > binaryData ( ) . length ( ) > 0 ) {
QByteArray clientKeyPEM = readJob - > binaryData ( ) ;
2019-08-27 04:32:21 +03:00
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
// load whatever we have. So we try until it works.
2019-12-24 09:12:54 +03:00
_clientSslKey = QSslKey ( clientKeyPEM , QSsl : : Rsa ) ;
2019-08-27 04:32:21 +03:00
if ( _clientSslKey . isNull ( ) ) {
2019-12-24 09:12:54 +03:00
_clientSslKey = QSslKey ( clientKeyPEM , QSsl : : Dsa ) ;
2019-08-27 04:32:21 +03:00
}
if ( _clientSslKey . isNull ( ) ) {
2019-12-24 09:12:54 +03:00
_clientSslKey = QSslKey ( clientKeyPEM , QSsl : : Ec ) ;
2019-08-27 04:32:21 +03:00
}
if ( _clientSslKey . isNull ( ) ) {
qCWarning ( lcWebFlowCredentials ) < < " Could not load SSL key into Qt! " ;
}
2019-12-24 09:12:54 +03:00
clientKeyPEM . clear ( ) ;
} else {
qCWarning ( lcWebFlowCredentials ) < < " Unable to read client key " < < readJob - > errorString ( ) ;
2019-08-27 04:32:21 +03:00
}
2019-12-24 09:12:54 +03:00
readJob - > deleteLater ( ) ;
2019-08-30 05:56:01 +03:00
// Start fetching client CA certs
_clientSslCaCertificates . clear ( ) ;
2019-08-27 10:55:41 +03:00
2019-08-30 05:56:01 +03:00
readSingleClientCaCertPEM ( ) ;
}
void WebFlowCredentials : : readSingleClientCaCertPEM ( )
{
// try to fetch a client ca cert
if ( _clientSslCaCertificates . count ( ) < _clientSslCaCertificatesMaxCount ) {
2019-12-24 09:12:54 +03:00
auto * job = new KeychainChunk : : ReadJob ( _account ,
_user + clientCaCertificatePEMC + QString : : number ( _clientSslCaCertificates . count ( ) ) ,
_keychainMigration ) ;
connect ( job , & KeychainChunk : : ReadJob : : finished , this , & WebFlowCredentials : : slotReadClientCaCertsPEMJobDone ) ;
2019-08-30 05:56:01 +03:00
job - > start ( ) ;
} else {
2019-12-08 01:27:57 +03:00
qCWarning ( lcWebFlowCredentials ) < < " Maximum client CA cert count exceeded while reading, ignoring after " < < _clientSslCaCertificatesMaxCount ;
2019-08-30 05:56:01 +03:00
slotReadClientCaCertsPEMJobDone ( nullptr ) ;
}
2019-08-27 10:55:41 +03:00
}
2019-12-24 09:12:54 +03:00
void WebFlowCredentials : : slotReadClientCaCertsPEMJobDone ( KeychainChunk : : ReadJob * readJob ) {
// Store cert in memory
2019-08-30 05:56:01 +03:00
if ( readJob ) {
if ( readJob - > error ( ) = = NoError & & readJob - > binaryData ( ) . length ( ) > 0 ) {
QList < QSslCertificate > sslCertificateList = QSslCertificate : : fromData ( readJob - > binaryData ( ) , QSsl : : Pem ) ;
if ( sslCertificateList . length ( ) > = 1 ) {
_clientSslCaCertificates . append ( sslCertificateList . at ( 0 ) ) ;
}
2019-12-24 09:12:54 +03:00
readJob - > deleteLater ( ) ;
2019-08-30 05:56:01 +03:00
// try next cert
readSingleClientCaCertPEM ( ) ;
return ;
} else {
if ( readJob - > error ( ) ! = QKeychain : : Error : : EntryNotFound | |
2019-09-09 19:52:36 +03:00
( ( readJob - > error ( ) = = QKeychain : : Error : : EntryNotFound ) & & _clientSslCaCertificates . count ( ) = = 0 ) ) {
2019-12-08 01:27:57 +03:00
qCWarning ( lcWebFlowCredentials ) < < " Unable to read client CA cert slot " < < QString : : number ( _clientSslCaCertificates . count ( ) ) < < readJob - > errorString ( ) ;
2019-08-30 05:56:01 +03:00
}
2019-08-27 10:55:41 +03:00
}
2019-12-24 09:12:54 +03:00
readJob - > deleteLater ( ) ;
2019-08-27 10:55:41 +03:00
}
2019-08-27 04:32:21 +03:00
// Now fetch the actual server password
2017-11-28 22:06:27 +03:00
const QString kck = keychainKey (
_account - > url ( ) . toString ( ) ,
_user ,
2018-11-02 11:49:43 +03:00
_keychainMigration ? QString ( ) : _account - > id ( ) ) ;
2017-11-28 22:06:27 +03:00
2020-05-18 21:54:23 +03:00
auto * job = new ReadPasswordJob ( Theme : : instance ( ) - > appName ( ) ) ;
2019-12-24 09:12:54 +03:00
# if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
2019-08-27 04:32:21 +03:00
addSettingsToJob ( _account , job ) ;
2019-12-24 09:12:54 +03:00
# endif
2017-11-28 22:06:27 +03:00
job - > setInsecureFallback ( false ) ;
job - > setKey ( kck ) ;
connect ( job , & Job : : finished , this , & WebFlowCredentials : : slotReadPasswordJobDone ) ;
job - > start ( ) ;
}
void WebFlowCredentials : : slotReadPasswordJobDone ( Job * incomingJob ) {
2020-05-18 21:54:23 +03:00
auto * job = qobject_cast < ReadPasswordJob * > ( incomingJob ) ;
2017-11-28 22:06:27 +03:00
QKeychain : : Error error = job - > error ( ) ;
2018-11-02 11:49:43 +03:00
// If we could not find the entry try the old entries
if ( ! _keychainMigration & & error = = QKeychain : : EntryNotFound ) {
_keychainMigration = true ;
fetchFromKeychainHelper ( ) ;
return ;
}
2019-08-27 04:32:21 +03:00
if ( _user . isEmpty ( ) ) {
qCWarning ( lcWebFlowCredentials ) < < " Strange: User is empty! " ;
}
2017-11-28 22:06:27 +03:00
if ( error = = QKeychain : : NoError ) {
_password = job - > textData ( ) ;
_ready = true ;
_credentialsValid = true ;
} else {
_ready = false ;
}
emit fetched ( ) ;
2018-11-02 11:49:43 +03:00
2019-12-24 09:12:54 +03:00
job - > deleteLater ( ) ;
2018-11-02 11:49:43 +03:00
// If keychain data was read from legacy location, wipe these entries and store new ones
if ( _keychainMigration & & _ready ) {
_keychainMigration = false ;
persist ( ) ;
2019-08-30 06:35:36 +03:00
deleteKeychainEntries ( true ) ; // true: delete old entries
2019-02-15 22:23:24 +03:00
qCInfo ( lcWebFlowCredentials ) < < " Migrated old keychain entries " ;
2018-11-02 11:49:43 +03:00
}
}
2019-08-30 06:35:36 +03:00
void WebFlowCredentials : : deleteKeychainEntries ( bool oldKeychainEntries ) {
2019-12-24 09:12:54 +03:00
auto startDeleteJob = [ this , oldKeychainEntries ] ( QString key ) {
2020-05-18 21:54:23 +03:00
auto * job = new DeletePasswordJob ( Theme : : instance ( ) - > appName ( ) ) ;
2019-12-24 09:12:54 +03:00
# if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
2019-08-27 04:32:21 +03:00
addSettingsToJob ( _account , job ) ;
2019-12-24 09:12:54 +03:00
# endif
2019-12-08 03:59:50 +03:00
job - > setInsecureFallback ( false ) ;
2019-08-30 05:56:01 +03:00
job - > setKey ( keychainKey ( _account - > url ( ) . toString ( ) ,
2019-12-24 09:12:54 +03:00
key ,
2019-08-30 06:35:36 +03:00
oldKeychainEntries ? QString ( ) : _account - > id ( ) ) ) ;
2019-12-24 09:12:54 +03:00
connect ( job , & Job : : finished , this , [ ] ( QKeychain : : Job * job ) {
2020-05-18 21:54:23 +03:00
auto * djob = qobject_cast < DeletePasswordJob * > ( job ) ;
2019-12-24 09:12:54 +03:00
djob - > deleteLater ( ) ;
} ) ;
2019-08-27 04:32:21 +03:00
job - > start ( ) ;
} ;
startDeleteJob ( _user ) ;
2019-08-30 05:56:01 +03:00
2019-12-08 02:00:02 +03:00
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* TODO : For " Log out " & " Remove account " : Remove client CA certs and KEY !
*
* Disabled as long as selecting another cert is not supported by the UI .
*
* Being able to specify a new certificate is important anyway : expiry etc .
*
* We introduce this dirty hack here , to allow deleting them upon Remote Wipe .
*/
if ( _account - > isRemoteWipeRequested_HACK ( ) ) {
// <-- FIXME MS@2019-12-07
startDeleteJob ( _user + clientKeyPEMC ) ;
startDeleteJob ( _user + clientCertificatePEMC ) ;
for ( auto i = 0 ; i < _clientSslCaCertificates . count ( ) ; i + + ) {
startDeleteJob ( _user + clientCaCertificatePEMC + QString : : number ( i ) ) ;
}
2019-12-08 01:27:57 +03:00
# if defined(Q_OS_WIN)
2019-12-24 09:12:54 +03:00
// Also delete key / cert sub-chunks (Windows workaround)
// The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows
for ( auto chunk = 1 ; chunk < KeychainChunk : : MaxChunks ; chunk + + ) {
const QString strChunkSuffix = QString ( " . " ) + QString : : number ( chunk ) ;
startDeleteJob ( _user + clientKeyPEMC + strChunkSuffix ) ;
startDeleteJob ( _user + clientCertificatePEMC + strChunkSuffix ) ;
for ( auto i = 0 ; i < _clientSslCaCertificates . count ( ) ; i + + ) {
startDeleteJob ( _user + clientCaCertificatePEMC + QString : : number ( i ) ) ;
}
2019-12-08 02:00:02 +03:00
}
2019-12-08 01:27:57 +03:00
# endif
2019-12-08 02:00:02 +03:00
// FIXME MS@2019-12-07 -->
}
// <-- FIXME MS@2019-12-07
2017-11-25 22:19:25 +03:00
}
2019-12-24 09:12:54 +03:00
} // namespace OCC