2014-12-17 16:09:57 +03:00
/*
* Copyright ( C ) by Daniel Molkentin < danimo @ owncloud . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
2016-10-25 12:00:07 +03:00
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
2014-12-17 16:09:57 +03:00
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License
* for more details .
*/
# include "accountstate.h"
2015-04-09 17:19:17 +03:00
# include "accountmanager.h"
2019-07-24 14:56:21 +03:00
# include "remotewipe.h"
2014-12-17 16:09:57 +03:00
# include "account.h"
# include "creds/abstractcredentials.h"
2017-07-18 15:53:41 +03:00
# include "creds/httpcredentials.h"
2015-09-05 16:39:22 +03:00
# include "logger.h"
2015-10-20 10:25:42 +03:00
# include "configfile.h"
2014-12-17 16:09:57 +03:00
2015-06-15 16:04:39 +03:00
# include <QSettings>
2017-07-04 13:23:23 +03:00
# include <QTimer>
2015-08-30 16:20:35 +03:00
# include <qfontmetrics.h>
2014-12-17 16:09:57 +03:00
2019-07-24 14:56:21 +03:00
# include <QJsonDocument>
# include <QJsonObject>
# include <QNetworkRequest>
# include <QBuffer>
2014-12-17 16:09:57 +03:00
namespace OCC {
2017-12-28 22:33:10 +03:00
Q_LOGGING_CATEGORY ( lcAccountState , " nextcloud.gui.account.state " , QtInfoMsg )
2017-05-09 15:24:11 +03:00
2014-12-18 14:09:48 +03:00
AccountState : : AccountState ( AccountPtr account )
2015-04-23 16:59:32 +03:00
: QObject ( )
2014-12-17 16:09:57 +03:00
, _account ( account )
, _state ( AccountState : : Disconnected )
, _connectionStatus ( ConnectionValidator : : Undefined )
, _waitingForNewCredentials ( false )
2018-03-02 00:19:04 +03:00
, _notificationsEtagResponseHeader ( " * " )
2018-07-02 14:02:15 +03:00
, _maintenanceToConnectedDelay ( 60000 + ( qrand ( ) % ( 4 * 60000 ) ) ) // 1-5min delay
2019-07-24 14:56:21 +03:00
, _remoteWipe ( new RemoteWipe ( _account ) )
2014-12-17 16:09:57 +03:00
{
qRegisterMetaType < AccountState * > ( " AccountState* " ) ;
2017-09-20 11:14:48 +03:00
connect ( account . data ( ) , & Account : : invalidCredentials ,
2019-07-24 14:56:21 +03:00
this , & AccountState : : slotHandleRemoteWipeCheck ) ;
2017-09-20 11:14:48 +03:00
connect ( account . data ( ) , & Account : : credentialsFetched ,
this , & AccountState : : slotCredentialsFetched ) ;
connect ( account . data ( ) , & Account : : credentialsAsked ,
this , & AccountState : : slotCredentialsAsked ) ;
2015-10-19 12:50:26 +03:00
_timeSinceLastETagCheck . invalidate ( ) ;
2014-12-17 16:09:57 +03:00
}
AccountState : : ~ AccountState ( )
{
}
2016-03-01 18:08:23 +03:00
AccountState * AccountState : : loadFromSettings ( AccountPtr account , QSettings & /*settings*/ )
{
auto accountState = new AccountState ( account ) ;
return accountState ;
}
void AccountState : : writeToSettings ( QSettings & /*settings*/ )
{
}
2014-12-18 14:09:48 +03:00
AccountPtr AccountState : : account ( ) const
2014-12-17 16:09:57 +03:00
{
2015-04-17 18:56:17 +03:00
return _account ;
2014-12-17 16:09:57 +03:00
}
AccountState : : ConnectionStatus AccountState : : connectionStatus ( ) const
{
return _connectionStatus ;
}
QStringList AccountState : : connectionErrors ( ) const
{
return _connectionErrors ;
}
AccountState : : State AccountState : : state ( ) const
{
return _state ;
}
void AccountState : : setState ( State state )
{
if ( _state ! = state ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcAccountState ) < < " AccountState state change: "
2014-12-17 16:09:57 +03:00
< < stateString ( _state ) < < " -> " < < stateString ( state ) ;
State oldState = _state ;
_state = state ;
if ( _state = = SignedOut ) {
_connectionStatus = ConnectionValidator : : Undefined ;
_connectionErrors . clear ( ) ;
} else if ( oldState = = SignedOut & & _state = = Disconnected ) {
2017-05-08 13:39:08 +03:00
// If we stop being voluntarily signed-out, try to connect and
// auth right now!
checkConnectivity ( ) ;
} else if ( _state = = ServiceUnavailable ) {
// Check if we are actually down for maintenance.
// To do this we must clear the connection validator that just
// produced the 503. It's finished anyway and will delete itself.
_connectionValidator . clear ( ) ;
2016-01-21 15:33:03 +03:00
checkConnectivity ( ) ;
2014-12-17 16:09:57 +03:00
}
2016-04-28 23:43:53 +03:00
if ( oldState = = Connected | | _state = = Connected ) {
emit isConnectedChanged ( ) ;
}
2014-12-17 16:09:57 +03:00
}
2015-05-15 13:26:23 +03:00
// might not have changed but the underlying _connectionErrors might have
emit stateChanged ( _state ) ;
2014-12-17 16:09:57 +03:00
}
QString AccountState : : stateString ( State state )
{
switch ( state ) {
case SignedOut :
2015-07-01 13:30:18 +03:00
return tr ( " Signed out " ) ;
2014-12-17 16:09:57 +03:00
case Disconnected :
2015-07-01 13:30:18 +03:00
return tr ( " Disconnected " ) ;
2014-12-17 16:09:57 +03:00
case Connected :
2015-07-01 13:30:18 +03:00
return tr ( " Connected " ) ;
2015-04-24 12:32:47 +03:00
case ServiceUnavailable :
2015-07-01 13:30:18 +03:00
return tr ( " Service unavailable " ) ;
2017-05-08 13:39:08 +03:00
case MaintenanceMode :
return tr ( " Maintenance mode " ) ;
2014-12-17 16:09:57 +03:00
case NetworkError :
2015-07-01 13:30:18 +03:00
return tr ( " Network error " ) ;
2014-12-17 16:09:57 +03:00
case ConfigurationError :
2015-07-01 13:30:18 +03:00
return tr ( " Configuration error " ) ;
2017-07-13 12:27:02 +03:00
case AskingCredentials :
return tr ( " Asking Credentials " ) ;
2014-12-17 16:09:57 +03:00
}
2015-07-01 13:30:18 +03:00
return tr ( " Unknown account state " ) ;
2014-12-17 16:09:57 +03:00
}
bool AccountState : : isSignedOut ( ) const
{
return _state = = SignedOut ;
}
2015-12-09 13:06:28 +03:00
void AccountState : : signOutByUi ( )
2014-12-17 16:09:57 +03:00
{
2015-12-09 13:06:28 +03:00
account ( ) - > credentials ( ) - > forgetSensitiveData ( ) ;
setState ( SignedOut ) ;
}
2017-11-05 21:50:09 +03:00
void AccountState : : freshConnectionAttempt ( )
{
if ( isConnected ( ) )
setState ( Disconnected ) ;
checkConnectivity ( ) ;
}
2015-12-09 13:06:28 +03:00
void AccountState : : signIn ( )
{
if ( _state = = SignedOut ) {
2017-07-21 16:54:47 +03:00
_waitingForNewCredentials = false ;
2014-12-17 16:09:57 +03:00
setState ( Disconnected ) ;
}
}
bool AccountState : : isConnected ( ) const
{
return _state = = Connected ;
}
2015-10-19 12:50:26 +03:00
void AccountState : : tagLastSuccessfullETagRequest ( )
{
2017-05-29 11:48:21 +03:00
_timeSinceLastETagCheck . start ( ) ;
2015-10-19 12:50:26 +03:00
}
2018-03-02 00:19:04 +03:00
QByteArray AccountState : : notificationsEtagResponseHeader ( ) const
{
return _notificationsEtagResponseHeader ;
}
void AccountState : : setNotificationsEtagResponseHeader ( const QByteArray & value )
{
_notificationsEtagResponseHeader = value ;
}
2018-04-10 16:38:59 +03:00
QByteArray AccountState : : navigationAppsEtagResponseHeader ( ) const
{
return _navigationAppsEtagResponseHeader ;
}
void AccountState : : setNavigationAppsEtagResponseHeader ( const QByteArray & value )
{
_navigationAppsEtagResponseHeader = value ;
}
2016-01-21 15:33:03 +03:00
void AccountState : : checkConnectivity ( )
2014-12-17 16:09:57 +03:00
{
if ( isSignedOut ( ) | | _waitingForNewCredentials ) {
return ;
}
2015-05-15 13:26:23 +03:00
if ( _connectionValidator ) {
2017-03-30 14:46:20 +03:00
qCWarning ( lcAccountState ) < < " ConnectionValidator already running, ignoring " < < account ( ) - > displayName ( ) ;
2015-05-15 13:26:23 +03:00
return ;
}
2015-10-19 12:50:26 +03:00
2017-04-27 14:58:26 +03:00
// If we never fetched credentials, do that now - otherwise connection attempts
// make little sense, we might be missing client certs.
if ( ! account ( ) - > credentials ( ) - > wasFetched ( ) ) {
_waitingForNewCredentials = true ;
account ( ) - > credentials ( ) - > fetchFromKeychain ( ) ;
return ;
}
2015-10-19 12:50:26 +03:00
// IF the account is connected the connection check can be skipped
// if the last successful etag check job is not so long ago.
2015-10-20 10:25:42 +03:00
ConfigFile cfg ;
2018-01-24 00:51:25 +03:00
std : : chrono : : milliseconds polltime = cfg . remotePollInterval ( ) ;
2015-10-20 10:25:42 +03:00
2015-10-19 12:50:26 +03:00
if ( isConnected ( ) & & _timeSinceLastETagCheck . isValid ( )
2018-01-24 00:51:25 +03:00
& & _timeSinceLastETagCheck . hasExpired ( polltime . count ( ) ) ) {
qCDebug ( lcAccountState ) < < account ( ) - > displayName ( ) < < " The last ETag check succeeded within the last " < < polltime . count ( ) / 1000 < < " secs. No connection check needed! " ;
2015-10-19 12:50:26 +03:00
return ;
}
2015-09-05 16:39:22 +03:00
ConnectionValidator * conValidator = new ConnectionValidator ( account ( ) ) ;
2015-05-15 13:26:23 +03:00
_connectionValidator = conValidator ;
2017-09-20 12:48:13 +03:00
connect ( conValidator , & ConnectionValidator : : connectionResult ,
this , & AccountState : : slotConnectionValidatorResult ) ;
2014-12-17 16:09:57 +03:00
if ( isConnected ( ) ) {
// Use a small authed propfind as a minimal ping when we're
// already connected.
conValidator - > checkAuthentication ( ) ;
} else {
// Check the server and then the auth.
2015-03-19 13:40:47 +03:00
2016-05-31 11:20:29 +03:00
// Let's try this for all OS and see if it fixes the Qt issues we have on Linux #4720 #3888 #4051
//#ifdef Q_OS_WIN
2015-03-19 13:40:47 +03:00
// There seems to be a bug in Qt on Windows where QNAM sometimes stops
// working correctly after the computer woke up from sleep. See #2895 #2899
// and #2973.
// As an attempted workaround, reset the QNAM regularly if the account is
// disconnected.
account ( ) - > resetNetworkAccessManager ( ) ;
2015-07-02 12:28:40 +03:00
// If we don't reset the ssl config a second CheckServerJob can produce a
// ssl config that does not have a sensible certificate chain.
account ( ) - > setSslConfiguration ( QSslConfiguration ( ) ) ;
2016-05-31 11:20:29 +03:00
//#endif
2014-12-17 16:09:57 +03:00
conValidator - > checkServerAndAuth ( ) ;
}
}
void AccountState : : slotConnectionValidatorResult ( ConnectionValidator : : Status status , const QStringList & errors )
{
if ( isSignedOut ( ) ) {
2018-02-21 17:12:21 +03:00
qCWarning ( lcAccountState ) < < " Signed out, ignoring " < < status < < _account - > url ( ) . toString ( ) ;
2014-12-17 16:09:57 +03:00
return ;
}
2017-07-04 13:23:23 +03:00
// Come online gradually from 503 or maintenance mode
if ( status = = ConnectionValidator : : Connected
& & ( _connectionStatus = = ConnectionValidator : : ServiceUnavailable
| | _connectionStatus = = ConnectionValidator : : MaintenanceMode ) ) {
if ( ! _timeSinceMaintenanceOver . isValid ( ) ) {
qCInfo ( lcAccountState ) < < " AccountState reconnection: delaying for "
< < _maintenanceToConnectedDelay < < " ms " ;
_timeSinceMaintenanceOver . start ( ) ;
2017-09-20 11:14:48 +03:00
QTimer : : singleShot ( _maintenanceToConnectedDelay + 100 , this , & AccountState : : checkConnectivity ) ;
2017-07-04 13:23:23 +03:00
return ;
} else if ( _timeSinceMaintenanceOver . elapsed ( ) < _maintenanceToConnectedDelay ) {
qCInfo ( lcAccountState ) < < " AccountState reconnection: only "
< < _timeSinceMaintenanceOver . elapsed ( ) < < " ms have passed " ;
return ;
}
}
2014-12-17 16:09:57 +03:00
if ( _connectionStatus ! = status ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcAccountState ) < < " AccountState connection status change: "
2018-02-21 17:12:21 +03:00
< < _connectionStatus < < " -> "
< < status ;
2014-12-17 16:09:57 +03:00
_connectionStatus = status ;
}
_connectionErrors = errors ;
switch ( status ) {
case ConnectionValidator : : Connected :
2015-07-07 15:44:16 +03:00
if ( _state ! = Connected ) {
2015-06-08 13:14:37 +03:00
setState ( Connected ) ;
}
2014-12-17 16:09:57 +03:00
break ;
case ConnectionValidator : : Undefined :
case ConnectionValidator : : NotConfigured :
setState ( Disconnected ) ;
break ;
case ConnectionValidator : : ServerVersionMismatch :
setState ( ConfigurationError ) ;
break ;
2015-02-11 11:23:04 +03:00
case ConnectionValidator : : StatusNotFound :
// This can happen either because the server does not exist
// or because we are having network issues. The latter one is
// much more likely, so keep trying to connect.
setState ( NetworkError ) ;
break ;
2017-04-27 14:58:26 +03:00
case ConnectionValidator : : CredentialsWrong :
case ConnectionValidator : : CredentialsNotReady :
2019-07-24 14:56:21 +03:00
handleInvalidCredentials ( ) ;
2014-12-17 16:09:57 +03:00
break ;
2017-04-27 14:58:26 +03:00
case ConnectionValidator : : SslError :
2015-01-21 17:30:25 +03:00
setState ( SignedOut ) ;
break ;
2015-04-24 12:32:47 +03:00
case ConnectionValidator : : ServiceUnavailable :
2017-07-04 13:23:23 +03:00
_timeSinceMaintenanceOver . invalidate ( ) ;
2015-04-24 12:32:47 +03:00
setState ( ServiceUnavailable ) ;
2015-02-25 11:49:39 +03:00
break ;
2017-05-08 13:39:08 +03:00
case ConnectionValidator : : MaintenanceMode :
2017-07-04 13:23:23 +03:00
_timeSinceMaintenanceOver . invalidate ( ) ;
2017-05-08 13:39:08 +03:00
setState ( MaintenanceMode ) ;
break ;
2014-12-17 16:09:57 +03:00
case ConnectionValidator : : Timeout :
setState ( NetworkError ) ;
break ;
}
}
2019-07-24 14:56:21 +03:00
void AccountState : : slotHandleRemoteWipeCheck ( )
{
// make sure it changes account state and icons
signOutByUi ( ) ;
qCInfo ( lcAccountState ) < < " Invalid credentials for " < < _account - > url ( ) . toString ( )
< < " checking for remote wipe request " ;
_waitingForNewCredentials = false ;
setState ( SignedOut ) ;
}
void AccountState : : handleInvalidCredentials ( )
2014-12-17 16:09:57 +03:00
{
2015-09-05 16:37:20 +03:00
if ( isSignedOut ( ) | | _waitingForNewCredentials )
2014-12-17 16:09:57 +03:00
return ;
2015-09-05 16:37:20 +03:00
2017-04-27 14:58:26 +03:00
qCInfo ( lcAccountState ) < < " Invalid credentials for " < < _account - > url ( ) . toString ( )
< < " asking user " ;
2017-07-18 15:53:41 +03:00
_waitingForNewCredentials = true ;
setState ( AskingCredentials ) ;
if ( account ( ) - > credentials ( ) - > ready ( ) ) {
2015-09-05 16:37:20 +03:00
account ( ) - > credentials ( ) - > invalidateToken ( ) ;
2018-06-13 16:15:54 +03:00
}
if ( auto creds = qobject_cast < HttpCredentials * > ( account ( ) - > credentials ( ) ) ) {
if ( creds - > refreshAccessToken ( ) )
return ;
2017-07-18 15:53:41 +03:00
}
2017-04-27 14:58:26 +03:00
account ( ) - > credentials ( ) - > askFromUser ( ) ;
2014-12-17 16:09:57 +03:00
}
2019-07-24 14:56:21 +03:00
2017-04-27 14:58:26 +03:00
void AccountState : : slotCredentialsFetched ( AbstractCredentials * )
2015-09-05 16:39:22 +03:00
{
2017-04-27 14:58:26 +03:00
// Make a connection attempt, no matter whether the credentials are
// ready or not - we want to check whether we can get an SSL connection
// going before bothering the user for a password.
qCInfo ( lcAccountState ) < < " Fetched credentials for " < < _account - > url ( ) . toString ( )
< < " attempting to connect " ;
2015-09-05 16:39:22 +03:00
_waitingForNewCredentials = false ;
2016-01-21 15:33:03 +03:00
checkConnectivity ( ) ;
2015-09-05 16:39:22 +03:00
}
void AccountState : : slotCredentialsAsked ( AbstractCredentials * credentials )
2014-12-17 16:09:57 +03:00
{
2017-04-27 14:58:26 +03:00
qCInfo ( lcAccountState ) < < " Credentials asked for " < < _account - > url ( ) . toString ( )
< < " are they ready? " < < credentials - > ready ( ) ;
2014-12-17 16:09:57 +03:00
_waitingForNewCredentials = false ;
if ( ! credentials - > ready ( ) ) {
// User canceled the connection or did not give a password
setState ( SignedOut ) ;
return ;
}
AccountState: Attempt to fix a crash
Backtrace from the crash reporter:
Crash: EXCEPTION_ACCESS_VIOLATION_READ at 0x21
File "qcoreapplication.cpp", line 1281, in QCoreApplication::postEvent
File "qobject.cpp", line 2125, in QObject::deleteLater
File "connectionvalidator.cpp", line 240, in OCC::ConnectionValidator::reportResult
File "connectionvalidator.cpp", line 206, in OCC::ConnectionValidator::slotAuthFailed
File "moc_connectionvalidator.cpp", line 127, in OCC::ConnectionValidator::qt_static_metacall
File "qobject.cpp", line 3716, in QMetaObject::activate
File "moc_networkjobs.cpp", line 653, in OCC::PropfindJob::finishedWithError
File "networkjobs.cpp", line 570, in OCC::PropfindJob::finished
I believe the problem is caused because 'this' was deleted in ConnectionValidator::reportResult
as the signal connectionResult gets emited. The AccountState::slotConnectionValidatorResult
slot does indeed call slotInvalidCredentials which might call {Shibboleth,Http}Credentials::fetchFromKeychain
which might emit fetched directly, which will call AccountState::slotCredentialsFetched
which deletes the _connectionValidator
So use deleteLater when deleting the _connectionValidator, hoping this helps
2017-03-28 16:11:58 +03:00
if ( _connectionValidator ) {
// When new credentials become available we always want to restart the
// connection validation, even if it's currently running.
_connectionValidator - > deleteLater ( ) ;
2018-11-11 12:56:22 +03:00
_connectionValidator = nullptr ;
AccountState: Attempt to fix a crash
Backtrace from the crash reporter:
Crash: EXCEPTION_ACCESS_VIOLATION_READ at 0x21
File "qcoreapplication.cpp", line 1281, in QCoreApplication::postEvent
File "qobject.cpp", line 2125, in QObject::deleteLater
File "connectionvalidator.cpp", line 240, in OCC::ConnectionValidator::reportResult
File "connectionvalidator.cpp", line 206, in OCC::ConnectionValidator::slotAuthFailed
File "moc_connectionvalidator.cpp", line 127, in OCC::ConnectionValidator::qt_static_metacall
File "qobject.cpp", line 3716, in QMetaObject::activate
File "moc_networkjobs.cpp", line 653, in OCC::PropfindJob::finishedWithError
File "networkjobs.cpp", line 570, in OCC::PropfindJob::finished
I believe the problem is caused because 'this' was deleted in ConnectionValidator::reportResult
as the signal connectionResult gets emited. The AccountState::slotConnectionValidatorResult
slot does indeed call slotInvalidCredentials which might call {Shibboleth,Http}Credentials::fetchFromKeychain
which might emit fetched directly, which will call AccountState::slotCredentialsFetched
which deletes the _connectionValidator
So use deleteLater when deleting the _connectionValidator, hoping this helps
2017-03-28 16:11:58 +03:00
}
2015-05-22 10:38:44 +03:00
2016-01-21 15:33:03 +03:00
checkConnectivity ( ) ;
2014-12-17 16:09:57 +03:00
}
2015-07-02 14:31:42 +03:00
std : : unique_ptr < QSettings > AccountState : : settings ( )
2015-06-15 16:04:39 +03:00
{
2017-08-16 09:36:52 +03:00
auto s = ConfigFile : : settingsWithGroup ( QLatin1String ( " Accounts " ) ) ;
2015-06-15 16:04:39 +03:00
s - > beginGroup ( _account - > id ( ) ) ;
return s ;
}
2014-12-17 16:09:57 +03:00
} // namespace OCC