Files
kmess/src/accountsmanager.cpp
T
2026-07-06 09:37:59 +02:00

719 lines
21 KiB
C++

/***************************************************************************
accountsmanager.cpp - description
-------------------
begin : Sat May 3 2008
copyright : (C) 2008 by Valerio Pilo
email : valerio@kmess.org
***************************************************************************/
/***************************************************************************
* *
* 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 *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "accountsmanager.h"
#include "utils/kmessconfig.h"
#include "currentaccount.h"
#include "kmess.h"
#include "kmessapplication.h"
#include "kmessdebug.h"
#include <QDir>
#include <KConfig>
#include <KLocalizedString>
#include <KMessageBox>
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
#define KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
#endif
// Initialize the singleton instance to zero
AccountsManager* AccountsManager::instance_(0);
/**
* Constructor
*/
AccountsManager::AccountsManager()
: doPasswordRead_( false ),
doPasswordWrite_( false ),
passwordManager_( 0 )
{
// Load the saved accounts from disk and create them
readProperties();
}
// Destructor
AccountsManager::~AccountsManager()
{
// Delete the accounts
qDeleteAll( accounts_ );
accounts_.clear();
// Delete the password manager instance
delete passwordManager_;
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "DESTROYED.";
#endif
}
// Add a new account to the list
void AccountsManager::addAccount( Account *account )
{
// Append account
accounts_.append( account );
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "New account created:" << account->getHandle() << ".";
#endif
emit accountAdded( account );
}
// An account's settings have been changed
void AccountsManager::changeAccount( Account *account, QString oldHandle, QString oldFriendlyName )
{
if( KMESS_NULL( account ) ) return;
QString newHandle( account->getHandle() );
// We're saving settings for a new account
if( getAccountByHandle( oldHandle ) == 0 )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "Saving settings for new account" << newHandle;
#endif
// Add the account to the account list.
addAccount( account );
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
else
{
kmDebug() << "Saving settings for " << oldHandle;
}
#endif
// If the account is set to use autologin, make sure that all other accounts do not
if( ! account->isDeleted() && account->getUseAutologin() )
{
foreach( Account *otherAccount, accounts_ )
{
if( otherAccount != account )
{
otherAccount->setUseAutologin( false );
}
}
}
// Rename the account's directory if the handle has changed
if( oldHandle != newHandle )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "Moving account dir from" << oldHandle << "to" << newHandle;
#endif
QString accountsPath( KMessConfig::instance()->getAccountsDirectory() );
QDir accountsDir( accountsPath );
if( accountsDir.exists( newHandle ) )
{
kmWarning() << "Destination account directory" << newHandle << "already exists!";
}
if( accountsDir.rename( oldHandle, newHandle ) )
{
kmWarning() << "Unable to move account directory" << oldHandle << "to" << newHandle << "!";
}
}
// Commit configuration changes to disk, now
// Makes sure the settings are not lost if KMess is terminated or crashes.
account->saveProperties();
// Emit a signal so that the UI can be updated
emit accountChanged( account, oldHandle, oldFriendlyName );
}
// Verify is there is a saved account with the specified handle
bool AccountsManager::contains( Account *account ) const
{
if( account == CurrentAccount::instance() )
{
kmWarning() << "Called contains() on the current account!";
return true;
}
return accounts_.contains( account );
}
// Delete the given account
void AccountsManager::deleteAccount( Account *account )
{
if( KMESS_NULL(account) ) return;
emit accountDeleted( account );
QString handle( account->getHandle() );
KMessApplication *kmessApp = static_cast<KMessApplication *>(QApplication::instance());
if( kmessApp->getContactListWindow()->isConnected()
&& CurrentAccount::instance()->getHandle() == handle )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "Cannot delete current account:" << handle;
#endif
return;
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "Deleting account for " << handle << ".";
#endif
// Remove the account from the list of accounts
if( accounts_.removeAll( account ) == 0 )
{
kmWarning() << "Account" << handle << "not found in collection!";
}
// Delete the account directory
KMessConfig::instance()->destroyConfigDir( KMessConfig::instance()->getAccountsDirectory() + "/" + handle );
// Delete the account's contents
account->setDeleted();
account->deleteLater();
}
/**
* Delete the instance of the accounts manager
*/
void AccountsManager::destroy()
{
delete instance_;
instance_ = 0;
}
// Return the account for a given handle
Account *AccountsManager::getAccountByHandle( const QString &handle )
{
if( handle.isEmpty() )
{
return 0;
}
foreach( Account *account, accounts_ )
{
if( account->getHandle() == handle )
{
return account;
}
}
return 0;
}
// Get the list of all saved accounts
const QList<Account *> AccountsManager::getAccounts() const
{
return accounts_;
}
// Initialize the KWallet password manager
void AccountsManager::initializePasswordManager( bool block )
{
// Don't initialize if it is already
if( passwordManager_ != 0 )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "KWallet is already initialized.";
#endif
return;
}
// Obtain the window ID of the main KMess window
KMessApplication *kmessApp = static_cast<KMessApplication *>(QApplication::instance());
WId wId = kmessApp->getContactListWindow()->window()->winId();
if( block )
{
// Open KWallet (synchronously - blocks KMess until the password is given)
passwordManager_ = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), wId, KWallet::Wallet::Synchronous );
// Call the slot manually
slotWalletOpen( true );
return;
}
else
{
// Open KWallet (asynchronously - the slot will be called when - and if - the wallet will be open)
passwordManager_ = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), wId, KWallet::Wallet::Asynchronous );
if( passwordManager_ )
{
connect( passwordManager_, SIGNAL( walletOpened(bool) ),
this, SLOT ( slotWalletOpen(bool) ) );
}
else
{
// If the wallet service is not available, initialize the plain text password storage
slotWalletOpen( false );
return;
}
}
}
// Queue a read for all passwords
void AccountsManager::readPasswords( bool block )
{
doPasswordRead_ = true;
initializePasswordManager( block );
}
// Save passwords for all accounts (only actually saves when there are accounts to be updated!)
void AccountsManager::savePasswords( bool block )
{
bool passwordsToWrite = false;
// check if there are passwords to be written
foreach( const Account *account, accounts_ )
{
if( account->getSavedPassword() != account->getPassword() )
{
passwordsToWrite = true;
break;
}
}
if( passwordsToWrite )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "There were passwords to be updated, scheduling an update.";
#endif
doPasswordWrite_ = true;
initializePasswordManager( block );
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
else
{
kmDebug() << "There were no passwords to be updated, skipping save request.";
}
#endif
}
// Read the passwords from the just opened wallet
void AccountsManager::slotWalletOpen( bool success )
{
// if the wallet couldn't be opened, delete it
if( ! success ||
( passwordManager_ != 0 && ! passwordManager_->isOpen() ) )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Wallet opening failed, falling back to plain text passwords.";
#endif
delete passwordManager_;
passwordManager_ = 0;
}
// initialize the wallet
if( passwordManager_ )
{
setupPasswordManager();
}
// Read the passwords
if( doPasswordRead_ )
{
if( passwordManager_ == 0 )
{
KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) );
const QStringList passwordAccountList( passwordGroup.keyList() );
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Reading passwords from plain-text storage instead of KWallet!";
#endif
// Read the passwords from the KMess config file
foreach( const QString &accountHandle, passwordAccountList )
{
Account *account = getAccountByHandle( accountHandle );
// Skip passwords for invalid and deleted accounts, and remove them too
if( account == 0 )
{
passwordGroup.deleteEntry( accountHandle );
continue;
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Password have been read for account: " << accountHandle;
#endif
QString password = passwordGroup.readEntry( accountHandle, QString() );
account->setPassword( password );
account->setSavedPassword( password );
}
}
else // passwordManager_ != 0
{
QString password;
// Read the password for each account
foreach( Account *account, accounts_ )
{
if( passwordManager_->readPassword( account->getHandle(), password ) != 0 || password.isEmpty() )
{
continue;
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Password have been read for account: " << account->getHandle();
#endif
account->setPassword( password );
account->setSavedPassword( password );
}
}
emit passwordsReady();
}
// Write the passwords
if( doPasswordWrite_ )
{
if( passwordManager_ == 0 )
{
kmWarning() << "KWallet is not available: saving passwords in plain text.";
KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) );
// Store the password for each account
foreach( Account *account, accounts_ )
{
const QString& handle = account->getHandle();
// If we have already the entry, check if the user want still save the password..
if( passwordGroup.hasKey( handle ) )
{
// If the password should not be saved, then delete any already saved password
if( account->getSavePassword() == false || account->getPassword().isEmpty() )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Password for account" << handle << "removed.";
#endif
account->setSavedPassword( "" );
passwordGroup.deleteEntry( handle );
continue;
}
}
// Save the password
account->setSavedPassword( account->getPassword() );
passwordGroup.writeEntry( handle, account->getPassword() );
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Password for account" << handle << "saved.";
#endif
}
}
else // passwordManager_ != 0
{
// Store the password for each account
foreach( Account *account, accounts_ )
{
const QString& handle = account->getHandle();
bool savePassword = ( account->getSavePassword() && ! account->getPassword().isEmpty() );
// If the password should not be saved, then delete any already saved password
if( savePassword == false && passwordManager_->hasEntry( handle ) )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Removing account" << handle << "from wallet";
#endif
account->setSavedPassword( "" );
passwordManager_->removeEntry( handle );
continue;
}
// Update the password if we should
else if( savePassword == true && account->getSavedPassword() != account->getPassword()
&& passwordManager_->writePassword( handle, account->getPassword() ) != 0 )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Couldn't store password for account" << handle;
#endif
account->setSavedPassword( "" );
continue;
}
account->setSavedPassword( account->getPassword() );
}
// Save the passwords to disk now
passwordManager_->sync();
}
}
doPasswordRead_ = false;
doPasswordWrite_ = false;
// Clean up the password manager, it's not needed anymore
// (not cleaning it up could result in weird issues if the program crashes or so)
delete passwordManager_;
passwordManager_ = 0;
}
// Set up the passwordManager_ (change to the KMess folder, import plaintext passwords, etc)
void AccountsManager::setupPasswordManager()
{
#ifdef KMESSTEST
KMESS_ASSERT( passwordManager_->isOpen() );
#endif
// Look for an existing kmess folder and create one if needed
if( ! passwordManager_->hasFolder( "KMess" ) )
{
passwordManager_->createFolder( "KMess" );
}
// Set the current folder for password storing, it should't fail
if( ! passwordManager_->setFolder( "KMess" ) )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmWarning() << "Setting KWallet folder failed!";
#endif
delete passwordManager_;
passwordManager_ = 0;
return;
}
// Wallet is open: verify if the user has any useable plaintext password set.
// If so, ask if they should be deleted or copied to the wallet :)
KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) );
const QStringList passwordAccountList( passwordGroup.keyList() );
if( ! passwordAccountList.isEmpty() )
{
QStringList validAccounts;
foreach( const QString &accountHandle, passwordAccountList )
{
Account *account = getAccountByHandle( accountHandle );
// Skip passwords for invalid and deleted accounts, and remove them too
if( account == 0 )
{
passwordGroup.deleteEntry( accountHandle );
continue;
}
validAccounts << accountHandle;
}
// There are some accounts passwords. Ask the user
if( validAccounts.count() > 0 )
{
kmWarning() << "Insecure account passwords found for accounts: " << validAccounts;
int res = KMessageBox::questionTwoActionsCancel( 0,
i18n( "<html>Some insecurely stored account passwords have been found.<br />"
"Do you want to import the passwords to the KDE Wallet "
"named '%1', keep the insecurely stored passwords, or delete "
"them permanently?<br /><br />"
"<i>Note: it is not recommended to keep insecurely stored passwords "
"if the KDE Wallet is available, because your passwords "
"will be easily readable in the KMess configuration files.</i></html>",
KWallet::Wallet::LocalWallet() ),
i18nc( "Dialog box caption", "Secure Password Storage" ),
KGuiItem( i18nc( "Dialog button: Import passwords to a KDE wallet",
"Import" ),
"document-import" ), // Yes option
KGuiItem( i18nc( "Dialog button: Delete insecurely stored passwords",
"Delete" ),
"edit-delete-shred" ), // No option
KGuiItem( i18nc( "Dialog button: Keep insecurely stored passwords",
"Keep" ),
"mail-mark-notjunk" ), // Cancel option
"insecurePasswordsDetected" );
switch( res )
{
case KMessageBox::PrimaryAction:
// Import passwords to the wallet, and remove the plaintext ones
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Importing plaintext passwords to the wallet.";
#endif
foreach( const QString &accountHandle, passwordAccountList )
{
Account *account = getAccountByHandle( accountHandle );
if( account != 0 )
{
passwordManager_->writePassword( accountHandle, passwordGroup.readEntry( accountHandle, QString() ) );
passwordGroup.deleteEntry( accountHandle );
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Imported password for account:" << accountHandle;
#endif
}
}
break;
case KMessageBox::SecondaryAction:
// Only delete the plain text passwords
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Deleting plain text account passwords.";
#endif
foreach( const QString &accountHandle, passwordAccountList )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "Deleting plaintext password for account:" << accountHandle;
#endif
passwordGroup.deleteEntry( accountHandle );
}
break;
case KMessageBox::Cancel:
// Do nothing (useful if you wish to postpone the decision)
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "No action against plaintext passwords will be taken now.";
#endif
break;
}
}
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
kmDebug() << "KWallet successfully opened.";
#endif
}
/**
* Return a singleton instance of the accounts manager
*/
AccountsManager* AccountsManager::instance()
{
// If the instance is null, create a new accounts manager and return that.
if ( instance_ == 0 )
{
instance_ = new AccountsManager();
}
return instance_;
}
/**
* Load the accounts from disk
*/
void AccountsManager::readProperties()
{
// Obtain the list of account directories in the KMess config directory
QStringList accountList( KMessConfig::instance()->getAccountsList() );
// Add each one of the saved accounts to the internal list
foreach( const QString &handle, accountList )
{
if( Account::isValidHandle( handle ) )
{
Account *account = new Account();
account->readProperties( handle );
addAccount( account );
}
}
}
// Show the settings dialog for a given account
void AccountsManager::showAccountSettings( Account *account, QWidget *parentWindow, AccountSettingsDialog::Page startingPage )
{
// No account has been specified, create one
if( ! account )
{
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
kmDebug() << "Opening settings for a new account";
#endif
// Create an account to store the profile information
account = new Account();
// Assume the user *does* want to save it's settings,
// otherwise he/she would have used the main login dialog.
account->setGuestAccount( false );
}
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
else
{
kmDebug() << "Opening settings for " << account->getHandle();
}
#endif
CurrentAccount *currentAccount = CurrentAccount::instance();
KMess *kmess = static_cast<KMessApplication *>(QApplication::instance())->getContactListWindow();
bool isCurrentAccount = ( account->getHandle() == currentAccount->getHandle() );
bool isConnectedCurrentAccount = isCurrentAccount && ( kmess->isConnected() );
// If we are changing the current account, directly change the CurrentAccount object
// instead of the stored account object
if( isConnectedCurrentAccount )
{
account = currentAccount;
}
// Show the settings dialog
AccountSettingsDialog *accountSettingsDialog = AccountSettingsDialog::instance( parentWindow );
connect( accountSettingsDialog, SIGNAL( deleteAccount(Account*) ),
this, SLOT ( deleteAccount(Account*) ) );
connect( accountSettingsDialog, SIGNAL( changedSettings(Account*,QString,QString) ),
this, SLOT ( changeAccount(Account*,QString,QString) ) );
accountSettingsDialog->loadSettings( account, isConnectedCurrentAccount, startingPage );
accountSettingsDialog->show();
accountSettingsDialog->raise();
}