/** * This file is part of TelepathyQt * * @copyright Copyright (C) 2012 Collabora Ltd. * @copyright Copyright (C) 2012 Nokia Corporation * @license LGPL 2.1 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "TelepathyQt/debug-internal.h" #include "TelepathyQt/_gen/captcha-authentication.moc.hpp" #include "TelepathyQt/_gen/captcha-authentication-internal.moc.hpp" #include #include #include #include #include namespace Tp { // --- PendingCaptchaAnswer::PendingCaptchaAnswer(const QDBusPendingCall &call, const CaptchaAuthenticationPtr &object) : PendingOperation(object), mWatcher(new QDBusPendingCallWatcher(call, this)), mCaptcha(object), mChannel(mCaptcha->channel()) { debug() << "Calling Captcha.Answer"; if (mWatcher->isFinished()) { onAnswerFinished(); } else { // Connect the pending void connect(mWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onAnswerFinished())); } } PendingCaptchaAnswer::~PendingCaptchaAnswer() { } void PendingCaptchaAnswer::onAnswerFinished() { QDBusReply reply = mWatcher->reply(); if (!reply.isValid()) { warning().nospace() << "Captcha.Answer failed with " << reply.error().name() << ": " << reply.error().message(); setFinishedWithError(reply.error()); return; } debug() << "Captcha.Answer returned successfully"; // It might have been already opened - check if (mCaptcha->status() == CaptchaStatusLocalPending || mCaptcha->status() == CaptchaStatusRemotePending) { debug() << "Awaiting captcha to be answered from server"; // Wait until status becomes relevant connect(mCaptcha.data(), SIGNAL(statusChanged(Tp::CaptchaStatus)), SLOT(onCaptchaStatusChanged(Tp::CaptchaStatus))); } else { onCaptchaStatusChanged(mCaptcha->status()); } } void PendingCaptchaAnswer::onCaptchaStatusChanged(Tp::CaptchaStatus status) { if (status == CaptchaStatusSucceeded) { // Perfect. Close the channel now. connect(mChannel->requestClose(), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onRequestCloseFinished(Tp::PendingOperation*))); } else if (status == CaptchaStatusFailed || status == CaptchaStatusTryAgain) { warning() << "Captcha status changed to" << status << ", failing"; setFinishedWithError(mCaptcha->error(), mCaptcha->errorDetails().debugMessage()); } } void PendingCaptchaAnswer::onRequestCloseFinished(Tp::PendingOperation *operation) { if (operation->isError()) { // We cannot really fail just because the channel didn't close. Throw a warning instead. warning() << "Could not close the channel after a successful captcha answer!!" << operation->errorMessage(); } setFinished(); } PendingCaptchaCancel::PendingCaptchaCancel(const QDBusPendingCall &call, const CaptchaAuthenticationPtr &object) : PendingOperation(object), mWatcher(new QDBusPendingCallWatcher(call, this)), mCaptcha(object), mChannel(mCaptcha->channel()) { debug() << "Calling Captcha.Cancel"; if (mWatcher->isFinished()) { onCancelFinished(); } else { // Connect the pending void connect(mWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onCancelFinished())); } } PendingCaptchaCancel::~PendingCaptchaCancel() { } void PendingCaptchaCancel::onCancelFinished() { QDBusReply reply = mWatcher->reply(); if (!reply.isValid()) { warning().nospace() << "Captcha.Answer failed with " << reply.error().name() << ": " << reply.error().message(); setFinishedWithError(reply.error()); return; } debug() << "Captcha.Cancel returned successfully"; // Perfect. Close the channel now. connect(mChannel->requestClose(), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onRequestCloseFinished(Tp::PendingOperation*))); } void PendingCaptchaCancel::onRequestCloseFinished(Tp::PendingOperation *operation) { if (operation->isError()) { // We cannot really fail just because the channel didn't close. Throw a warning instead. warning() << "Could not close the channel after a successful captcha cancel!!" << operation->errorMessage(); } setFinished(); } // -- CaptchaAuthentication::Private::Private(CaptchaAuthentication *parent) : parent(parent) { } void CaptchaAuthentication::Private::extractCaptchaAuthenticationProperties(const QVariantMap &props) { canRetry = qdbus_cast(props[QLatin1String("CanRetryCaptcha")]); status = static_cast(qdbus_cast(props[QLatin1String("Status")])); } /** * \class CaptchaAuthentication * \ingroup clientchannel * \headerfile TelepathyQt/captcha-authentication.h * * \brief The CaptchaAuthentication class exposes CaptchaAuthentication's features for channels implementing it. * * A ServerAuthentication channel can implement a CaptchaAuthentication interface: this class exposes all the features * this interface provides in a high-level fashion. It is a mechanism for retrieving a captcha challenge from * a connection manager and answering it. * * This class is meant to be used just during authentication phase. It is useful just for platform-level handlers * which are meant to handle authentication - if you are implementing a client which is meant to live in a * Telepathy-aware platform, you probably won't need to handle this unless you have very special needs. * * Note that CaptchaAuthentication cannot be instantiated directly, instead the accessor method from * ServerAuthenticationChannel (ServerAuthenticationChannel::captchaAuthentication) should be used. * * See \ref async_model, \ref shared_ptr */ CaptchaAuthentication::CaptchaAuthentication(const ChannelPtr &channel) : Object(), mPriv(new Private(this)) { mPriv->channel = channel; } /** * Class destructor. */ CaptchaAuthentication::~CaptchaAuthentication() { delete mPriv; } /** * Return the channel associated with this captcha. * * CaptchaAuthentication is just a representation of an interface which can be implemented by * a ServerAuthentication channel. This function will return the channel implementing the interface * represented by this instance. * * Note that it is currently guaranteed the ChannelPtr returned by this function will be a ServerAuthenticationChannel. * * \return The channel implementing the CaptchaAuthentication interface represented by this instance. */ Tp::ChannelPtr CaptchaAuthentication::channel() const { return ChannelPtr(mPriv->channel); } /** * Return whether this channel supports updating its captchas or not. * * Some protocols allow their captchas to be reloaded providing new data to the user; for example, in case * the image provided is not easily readable. This function checks if this instance supports such a feature. * * Note that in case this function returns \c true, requestCaptchas can be called safely after a failed answer attempt. * * \return \c true if a new captcha can be fetched from this channel, \c false otherwise. */ bool CaptchaAuthentication::canRetry() const { return mPriv->canRetry; } /** * Return the current status of the captcha. * * \return The current status of the captcha. */ Tp::CaptchaStatus CaptchaAuthentication::status() const { return mPriv->status; } /** * Return the code of the last error happened on the interface. * * \return An error code describing the last error occurred. * \sa errorDetails */ QString CaptchaAuthentication::error() const { return mPriv->error; } /** * Return the details of the last error happened on the interface. * * \return Further details describing the last error occurred. * \sa error */ Connection::ErrorDetails CaptchaAuthentication::errorDetails() const { return Connection::ErrorDetails(mPriv->errorDetails); } /** * Request captcha challenges from the connection manager. * * Even if most protocols usually provide a single captcha challenge (OCR), for a variety * of reasons some of them could provide a number of different challenge types, requiring * one or more of them to be answered. * * This method initiates a request to the connection manager for obtaining the most compatible captcha * challenges available. It allows to supply a number of supported mimetypes and types, so that the * request will fail if the CM is unable to provide a challenge compatible with what the handler supports, * or will provide the best one available otherwise. * * Please note that all the challenges returned by this request must be answered in order for the authentication * to succeed. * * Note that if the CM supports retrying the captcha, this function can also be used to load a new set of captchas. * In general, if canRetry returns true, one can expect this function to always return a different set * of challenges which invalidates any other obtained previously. * * \param preferredMimeTypes A list of mimetypes supported by the handler, or an empty list if every * mimetype can be supported. * \param preferredTypes A list of challenge types supported by the handler. * \return A PendingCaptchas which will emit PendingCaptchas::finished when the request has been completed and all the payloads * have been downloaded. * \sa canRetry * \sa cancel * \sa answer */ PendingCaptchas *CaptchaAuthentication::requestCaptchas(const QStringList &preferredMimeTypes, ChallengeTypes preferredTypes) { // The captcha should be either LocalPending or TryAgain if (status() != CaptchaStatusLocalPending && status() != CaptchaStatusTryAgain) { warning() << "Status must be local pending or try again"; return new PendingCaptchas(TP_QT_ERROR_NOT_AVAILABLE, QLatin1String("Channel busy"), CaptchaAuthenticationPtr(this)); } ChannelPtr serverAuthChannel = ChannelPtr(mPriv->channel); return new PendingCaptchas( serverAuthChannel->interface()->GetCaptchas(), preferredMimeTypes, preferredTypes, CaptchaAuthenticationPtr(this)); } /** * Overloaded function. Convenience method when just a single captcha requires to be answered. * * Note that you need to answer only the last set of challenges returned, in case requestCaptchas * was invoked multiple times. * * Please note that if this operation succeeds, the channel will be closed right after. * * \param id The id of the challenge being answered. * \param response The answer of this challenge. * \return A PendingOperation which will emit PendingOperation::finished upon the outcome of the answer procedure. * Upon success, the operation will complete once the channel is closed. * \sa requestCaptchas * \sa answer */ Tp::PendingOperation *CaptchaAuthentication::answer(uint id, const QString &response) { QMap answers; answers.insert(id, response); return answer(answers); } /** * Answer a set of challenges. * * Challenges obtained with requestCaptchas should be answered using this method. Note that * every challenge returned by the last invocation of requestCaptchas must be answered * in order for the operation to succeed. * * Usually, most protocols will require just a single challenge to be answered: if that is the * case, you can use the convenience overload. * * Note that you need to answer only the last set of challenges returned, in case requestCaptchas * was invoked multiple times. * * Please note that if this operation succeeds, the channel will be closed right after. * * \param response A set of answers mapped by their id to the challenges obtained previously * \return A PendingOperation which will emit PendingOperation::finished upon the outcome of the answer procedure. * Upon success, the operation will complete once the channel is closed. * \sa requestCaptchas * \sa answer */ Tp::PendingOperation *CaptchaAuthentication::answer(const Tp::CaptchaAnswers &response) { // The captcha should be LocalPending or TryAgain if (status() != CaptchaStatusLocalPending) { warning() << "Status must be local pending"; return new PendingCaptchas(TP_QT_ERROR_NOT_AVAILABLE, QLatin1String("Channel busy"), CaptchaAuthenticationPtr(this)); } ChannelPtr serverAuthChannel = ChannelPtr(mPriv->channel); return new PendingCaptchaAnswer(serverAuthChannel->interface()->AnswerCaptchas(response), CaptchaAuthenticationPtr(this)); } /** * Cancel the current challenge. * * Please note that if this operation succeeds, the channel will be closed right after. * * Note that this function has not the same semantics as retry. The status of the CaptchaAuthentication * will change to Failed even if the channel supports retrying. This function should be called * only if the user refuses to answer any challenge. Instead, if the user wishes to retry, * you should just call requestCaptchas one more time. * * \param reason The reason why the challenge has been cancelled. * \param message A message detailing the cancel reason. * \return A PendingOperation which will emit PendingOperation::finished upon the outcome of the cancel procedure. * Upon success, the operation will complete once the channel is closed. * \sa requestCaptchas */ Tp::PendingOperation *CaptchaAuthentication::cancel(CaptchaCancelReason reason, const QString &message) { ChannelPtr serverAuthChannel = ChannelPtr(mPriv->channel); return new PendingCaptchaCancel(serverAuthChannel->interface()->CancelCaptcha( reason, message), CaptchaAuthenticationPtr(this)); } void CaptchaAuthentication::onPropertiesChanged(const QVariantMap &changedProperties, const QStringList &invalidatedProperties) { Q_UNUSED(invalidatedProperties); if (changedProperties.contains(QLatin1String("CaptchaStatus"))) { mPriv->status = static_cast(changedProperties.value(QLatin1String("CaptchaStatus")).value()); emit statusChanged(mPriv->status); } if (changedProperties.contains(QLatin1String("CaptchaErrorDetails"))) { mPriv->errorDetails = changedProperties.value(QLatin1String("CaptchaErrorDetails")).toMap(); } if (changedProperties.contains(QLatin1String("CaptchaError"))) { mPriv->error = changedProperties.value(QLatin1String("CaptchaError")).toString(); } } /** * \fn void CaptchaAuthentication::statusChanged(Tp::CaptchaStatus status) * * Emitted when the value of status() changes. * * \sa status The new status of this CaptchaAuthentication. */ } // Tp