diff options
author | Krzesimir Nowak <krzesimir@endocode.com> | 2013-07-30 13:19:22 +0400 |
---|---|---|
committer | Krzesimir Nowak <krzesimir@endocode.com> | 2013-08-01 18:53:43 +0400 |
commit | 78b6f4df01bd7fb5620fe5af7d4619896c17249f (patch) | |
tree | e9212b75afb6f86e0914e27e66525d7d8bd2e48f /src/creds/http | |
parent | b7e88aa2efbd004d58c98868abe64d7d6a8c6fa5 (diff) |
Move the creds/ and wizard/ directories one level higher.
Diffstat (limited to 'src/creds/http')
-rw-r--r-- | src/creds/http/credentialstore.cpp | 338 | ||||
-rw-r--r-- | src/creds/http/credentialstore.h | 132 | ||||
-rw-r--r-- | src/creds/http/httpconfigfile.cpp | 81 | ||||
-rw-r--r-- | src/creds/http/httpconfigfile.h | 40 |
4 files changed, 591 insertions, 0 deletions
diff --git a/src/creds/http/credentialstore.cpp b/src/creds/http/credentialstore.cpp new file mode 100644 index 000000000..6f4533531 --- /dev/null +++ b/src/creds/http/credentialstore.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (C) by Klaas Freitag <freitag@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 + * the Free Software Foundation; version 2 of the License. + * + * 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 <QtGui> +#include <QInputDialog> + +#include "config.h" + +#include "creds/http/credentialstore.h" +#include "creds/http/httpconfigfile.h" +#include "mirall/theme.h" + +#ifdef WITH_QTKEYCHAIN +#include <qtkeychain/keychain.h> +using namespace QKeychain; +#endif + +#define MAX_LOGIN_ATTEMPTS 3 + +namespace Mirall { + +CredentialStore *CredentialStore::_instance=0; +CredentialStore::CredState CredentialStore::_state = NotFetched; +QString CredentialStore::_passwd = QString::null; +QString CredentialStore::_user = QString::null; +QString CredentialStore::_url = QString::null; +QString CredentialStore::_errorMsg = QString::null; +#ifdef WITH_QTKEYCHAIN +CredentialStore::CredentialType CredentialStore::_type = KeyChain; +#else +CredentialStore::CredentialType CredentialStore::_type = Settings; +#endif + +CredentialStore::CredentialStore(QObject *parent) : + QObject(parent) +{ +} + +CredentialStore *CredentialStore::instance() +{ + if( !CredentialStore::_instance ) CredentialStore::_instance = new CredentialStore; + return CredentialStore::_instance; +} + +QString CredentialStore::password() const +{ + return _passwd; +} +QString CredentialStore::user() const +{ + return _user; +} + +CredentialStore::CredState CredentialStore::state() +{ + return _state; +} + +void CredentialStore::fetchCredentials() +{ + HttpConfigFile cfgFile; + + bool ok = false; + QString pwd; + _user = cfgFile.user(); + _url = cfgFile.ownCloudUrl(); + + QString key = keyChainKey(_url); + + if( key.isNull() ) { + qDebug() << "Can not fetch credentials, url is zero!"; + _state = Error; + emit( fetchCredentialsFinished(false) ); + return; + } + + switch( _type ) { + case CredentialStore::Settings: { + /* Read from config file. */ + _state = Fetching; + cfgFile.fixupOldPassword(); + if( cfgFile.passwordExists() ) { + pwd = cfgFile.password(); + ok = true; + } else { + ok = false; + _state = EntryNotFound; + } + break; + } + case CredentialStore::KeyChain: { + // If the credentials are here already, return. + if( _state == Ok || _state == AsyncWriting ) { + emit(fetchCredentialsFinished(true)); + return; + } + // otherwise fetch asynchronious. +#ifdef WITH_QTKEYCHAIN + _state = AsyncFetching; + if( !_user.isEmpty() ) { + ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); + job->setKey( key ); + + connect( job, SIGNAL(finished(QKeychain::Job*)), this, + SLOT(slotKeyChainReadFinished(QKeychain::Job*))); + job->start(); + } +#else + qDebug() << "QtKeyChain: Not yet implemented!"; + _state = Error; +#endif + break; + } + default: { + break; + } + } + + if( _state == Fetching ) { // ...but not AsyncFetching + if( ok ) { + _passwd = pwd; + _state = Ok; + } + if( !ok && _state == Fetching ) { + _state = Error; + } + + emit( fetchCredentialsFinished(ok) ); + } else { + // in case of AsyncFetching nothing happens here. The finished-Slot + // will emit the finish signal. + } +} + +void CredentialStore::reset() +{ + _state = NotFetched; + _user = QString::null; + _passwd = QString::null; +} + +QString CredentialStore::keyChainKey( const QString& url ) const +{ + QString u(url); + if( u.isEmpty() ) { + qDebug() << "Empty url in keyChain, error!"; + return QString::null; + } + if( _user.isEmpty() ) { + qDebug() << "Error: User is emty!"; + return QString::null; + } + + if( !u.endsWith(QChar('/')) ) { + u.append(QChar('/')); + } + + QString key = _user+QLatin1Char(':')+u; + return key; +} + +void CredentialStore::slotKeyChainReadFinished(QKeychain::Job* job) +{ +#ifdef WITH_QTKEYCHAIN + ReadPasswordJob *pwdJob = static_cast<ReadPasswordJob*>(job); + if( pwdJob ) { + switch( pwdJob->error() ) { + case QKeychain::NoError: + _passwd = pwdJob->textData(); +#ifdef Q_OS_LINUX + // Currently there is a bug in the keychain on linux that if no + // entry is there, an empty password comes back, but no error. + if( _passwd.isEmpty() ) { + _state = EntryNotFound; + _errorMsg = tr("No password entry found in keychain. Please reconfigure."); + } else +#endif + _state = Ok; + break; + case QKeychain::EntryNotFound: + _state = EntryNotFound; + break; + case QKeychain::CouldNotDeleteEntry: + _state = Error; + break; + case QKeychain::AccessDenied: + _state = AccessDenied; + break; + case QKeychain::NoBackendAvailable: + _state = NoKeychainBackend; + break; + case QKeychain::NotImplemented: + _state = NoKeychainBackend; + break; + case QKeychain::OtherError: + default: + _state = Error; + + } + /* In case there is no backend, tranparentely switch to Settings file. */ + if( _state == NoKeychainBackend ) { + qDebug() << "No Storage Backend, falling back to Settings mode."; + _type = CredentialStore::Settings; + fetchCredentials(_user); + return; + } + + if( _state == EntryNotFound ) { + // try to migrate. + } + + if( _state != Ok ) { + qDebug() << "Error with keychain: " << pwdJob->errorString(); + if(_errorMsg.isEmpty()) _errorMsg = pwdJob->errorString(); + } else { + _errorMsg = QString::null; + } + } else { + _state = Error; + qDebug() << "Error: KeyChain Read Password Job failed!"; + } + emit(fetchCredentialsFinished(_state == Ok)); +#else + (void) job; +#endif +} + +QString CredentialStore::errorMessage() +{ + return _errorMsg; +} + +void CredentialStore::setCredentials( const QString& url, const QString& user, + const QString& pwd ) +{ + _passwd = pwd; + _user = user; +#ifdef WITH_QTKEYCHAIN + _type = KeyChain; +#else + _type = Settings; +#endif + _url = url; + _state = Ok; +} + +void CredentialStore::saveCredentials( ) +{ + HttpConfigFile cfgFile; + QString key = keyChainKey(_url); + if( key.isNull() ) { + qDebug() << "Error: Can not save credentials, URL is zero!"; + return; + } +#ifdef WITH_QTKEYCHAIN +#endif + + cfgFile.setUser(_user); + switch( _type ) { + case CredentialStore::KeyChain: { +#ifdef WITH_QTKEYCHAIN + WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); + // Set password in KeyChain + job->setKey( key ); + job->setTextData(_passwd); + + connect( job, SIGNAL(finished(QKeychain::Job*)), this, + SLOT(slotKeyChainWriteFinished(QKeychain::Job*))); + _state = AsyncWriting; + job->start(); +#endif + } + break; + case CredentialStore::Settings: + cfgFile.setPassword( _passwd ); + reset(); + break; + default: + // unsupported. + break; + } +} + +void CredentialStore::slotKeyChainWriteFinished( QKeychain::Job *job ) +{ +#ifdef WITH_QTKEYCHAIN + WritePasswordJob *pwdJob = static_cast<WritePasswordJob*>(job); + if( pwdJob ) { + QKeychain::Error err = pwdJob->error(); + + if( err != QKeychain::NoError ) { + qDebug() << "Error with keychain: " << pwdJob->errorString(); + if( err == NoBackendAvailable || err == NotImplemented || + pwdJob->errorString().contains(QLatin1String("Could not open wallet"))) { + _state = NoKeychainBackend; + _type = Settings; + saveCredentials(); + } else { + _state = Error; + } + } else { + qDebug() << "Successfully stored password for user " << _user; + // Try to remove password formerly stored in the config file. + HttpConfigFile cfgFile; + cfgFile.removePassword(); + _state = NotFetched; + } + } else { + qDebug() << "Error: KeyChain Write Password Job failed!"; + _state = Error; + } +#else + (void) job; +#endif +} + +// Called if a user chooses to not store the password locally. +void CredentialStore::deleteKeyChainCredential( const QString& key ) +{ +#ifdef WITH_QTKEYCHAIN + // Start the remove job, do not care so much about the result. + DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); + job->setKey( key ); + job->start(); +#endif +} + +} diff --git a/src/creds/http/credentialstore.h b/src/creds/http/credentialstore.h new file mode 100644 index 000000000..2732e603c --- /dev/null +++ b/src/creds/http/credentialstore.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) by Klaas Freitag <freitag@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 + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#ifndef CREDENTIALSTORE_H +#define CREDENTIALSTORE_H + +#include <QObject> +#include <QInputDialog> + +namespace QKeychain { + class Job; +} + +namespace Mirall { + +/* + * This object holds the credential information of the ownCloud connection. It + * is implemented as a singleton. + * At startup of the client, at first the fetchCredentials() method must be called + * which tries to get credentials from one of the supported backends. To determine + * which backend should be used, MirallConfigFile::credentialType() is called as + * the backend is configured in the config file. + * + * The fetchCredentials() call changes the internal state of the credential store + * to one of + * Ok: There are credentials. Note that it's unknown if they are correct!! + * Fetching: The fetching is not yet finished. + * EntryNotFound: No password entry found in the storage. + * Error: A general error happened. + * After fetching has finished, signal fetchCredentialsFinished(bool) is emitted. + * The result can be retrieved with state() and password() and user() methods. + */ + +class CredentialStore : public QObject +{ + Q_OBJECT +public: + enum CredState { NotFetched = 0, + Ok, + Fetching, + AsyncFetching, + EntryNotFound, + AccessDenied, + NoKeychainBackend, + Error, + AsyncWriting }; + + enum CredentialType { + Settings = 0, + KeyChain + }; + + QString password( ) const; + QString user( ) const; + + /** + * @brief state + * @return the state of the Credentialstore. + */ + CredState state(); + + /** + * @brief fetchCredentials - start to retrieve user credentials. + * + * This method must be called first to retrieve the credentials. + * At the end, this method emits the fetchKeyChainFinished() signal. + */ + void fetchCredentials(); + + /** + * @brief instance - singleton pointer. + * @return the singleton pointer to access the object. + */ + static CredentialStore *instance(); + + /** + * @brief setCredentials - sets the user credentials. + * + * This function is called from the setup wizard to set the credentials + * int this store. Note that it does not store the password. + * The function also sets the state to ok. + * @param url - the connection url + * @param user - the user name + */ + void setCredentials( const QString& url, const QString& user, const QString& pwd); + + void saveCredentials( ); + + QString errorMessage(); + + void reset(); +signals: + /** + * @brief fetchCredentialsFinished + * + * emitted as soon as the fetching of the credentials has finished. + * If the parameter is true, there is a password and user. This does + * however, not say if the credentials are valid log in data. + * If false, the user pressed cancel. + */ + void fetchCredentialsFinished(bool); + +protected slots: + void slotKeyChainReadFinished( QKeychain::Job* ); + void slotKeyChainWriteFinished( QKeychain::Job* ); + +private: + explicit CredentialStore(QObject *parent = 0); + void deleteKeyChainCredential( const QString& ); + QString keyChainKey( const QString& ) const; + + static CredentialStore *_instance; + static CredState _state; + static QString _passwd; + static QString _user; + static QString _url; + static QString _errorMsg; + static CredentialType _type; +}; +} + +#endif // CREDENTIALSTORE_H diff --git a/src/creds/http/httpconfigfile.cpp b/src/creds/http/httpconfigfile.cpp new file mode 100644 index 000000000..366b6c1ee --- /dev/null +++ b/src/creds/http/httpconfigfile.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) by Krzesimir Nowak <krzesimir@endocode.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 + * the Free Software Foundation; version 2 of the License. + * + * 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 "creds/http/httpconfigfile.h" + +namespace Mirall +{ + +namespace +{ + +const char userC[] = "user"; +const char passwdC[] = "passwd"; +const char oldPasswdC[] = "password"; + +} // ns + +QString HttpConfigFile::user() const +{ + return retrieveData(QString(), QLatin1String(userC)).toString(); +} + +void HttpConfigFile::setUser(const QString& user) +{ + storeData(QString(), QLatin1String(userC), QVariant(user)); +} + +QString HttpConfigFile::password() const +{ + const QVariant passwd(retrieveData(QString(), QLatin1String(passwdC))); + + if (passwd.isValid()) { + return QString::fromUtf8(QByteArray::fromBase64(passwd.toByteArray())); + } + + return QString(); +} + +void HttpConfigFile::setPassword(const QString& password) +{ + QByteArray pwdba = password.toUtf8(); + storeData( QString(), QLatin1String(passwdC), QVariant(pwdba.toBase64()) ); + removeOldPassword(); +} + +bool HttpConfigFile::passwordExists() const +{ + dataExists(QString(), QLatin1String(passwdC)); +} + +void HttpConfigFile::removePassword() +{ + removeOldPassword(); + removeData(QString(), QLatin1String(passwdC)); +} + +void HttpConfigFile::fixupOldPassword() +{ + const QString old(QString::fromLatin1(oldPasswdC)); + + if (dataExists(QString(), old)) { + setPassword(retrieveData(QString(), old).toString()); + } +}; + +void HttpConfigFile::removeOldPassword() +{ + removeData(QString(), QLatin1String(oldPasswdC)); +} + +} // ns Mirall diff --git a/src/creds/http/httpconfigfile.h b/src/creds/http/httpconfigfile.h new file mode 100644 index 000000000..2e690054f --- /dev/null +++ b/src/creds/http/httpconfigfile.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) by Krzesimir Nowak <krzesimir@endocode.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 + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#ifndef MIRALL_CREDS_HTTP_CONFIG_FILE_H +#define MIRALL_CREDS_HTTP_CONFIG_FILE_H + +#include "mirall/mirallconfigfile.h" + +namespace Mirall +{ + +class HttpConfigFile : public MirallConfigFile +{ +public: + QString user() const; + void setUser(const QString& user); + + QString password() const; + void setPassword(const QString& password); + bool passwordExists() const; + void removePassword(); + void fixupOldPassword(); + +private: + void removeOldPassword(); +}; + +} // ns Mirall + +#endif |