File indexing completed on 2023-05-30 11:40:09

0001 /*
0002  * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
0003  *   @author Andre Moreira Magalhaes <andre.magalhaes@collabora.co.uk>
0004  * Copyright (C) 2011 David Edmundson <kde@davidedmundson.co.uk>
0005  * Copyright (C) 2011 Daniele E. Domenichelli <daniele.domenichelli@gmail.com>
0006  * Copyright (C) 2014 Martin Klapetek <mklapetek@kde.org>
0007  *
0008  * This library is free software; you can redistribute it and/or
0009  * modify it under the terms of the GNU Lesser General Public
0010  * License as published by the Free Software Foundation; either
0011  * version 2.1 of the License, or (at your option) any later version.
0012  *
0013  * This library is distributed in the hope that it will be useful,
0014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  * Lesser General Public License for more details.
0017  *
0018  * You should have received a copy of the GNU Lesser General Public
0019  * License along with this library; if not, write to the Free Software
0020  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0021  */
0022 
0023 #include "x-telepathy-password-auth-operation.h"
0024 #include "x-telepathy-password-prompt.h"
0025 
0026 #include <KAccounts/getcredentialsjob.h>
0027 #include <KAccounts/core.h>
0028 
0029 #include <QDebug>
0030 
0031 #include <KLocalizedString>
0032 #include <KSharedConfig>
0033 #include <KConfigGroup>
0034 
0035 #include <Accounts/Account>
0036 #include <Accounts/Manager>
0037 #include <Accounts/AccountService>
0038 #include <SignOn/Identity>
0039 
0040 XTelepathyPasswordAuthOperation::XTelepathyPasswordAuthOperation(
0041         const Tp::AccountPtr &account,
0042         int accountStorageId,
0043         Tp::Client::ChannelInterfaceSASLAuthenticationInterface *saslIface,
0044         bool canTryAgain) :
0045     Tp::PendingOperation(account),
0046     m_account(account),
0047     m_saslIface(saslIface),
0048     m_canTryAgain(canTryAgain),
0049     m_canFinish(false),
0050     m_accountStorageId(accountStorageId)
0051 {
0052     connect(m_saslIface,
0053             SIGNAL(SASLStatusChanged(uint,QString,QVariantMap)),
0054             SLOT(onSASLStatusChanged(uint,QString,QVariantMap)));
0055 
0056     m_config = KSharedConfig::openConfig(QStringLiteral("kaccounts-ktprc"));
0057     m_lastLoginFailedConfig = m_config->group(QStringLiteral("lastLoginFailed"));
0058 }
0059 
0060 XTelepathyPasswordAuthOperation::~XTelepathyPasswordAuthOperation()
0061 {
0062 }
0063 
0064 void XTelepathyPasswordAuthOperation::onSASLStatusChanged(uint status, const QString &reason,
0065         const QVariantMap &details)
0066 {
0067     if (status == Tp::SASLStatusNotStarted) {
0068         qDebug() << "Requesting password";
0069         // if we have non-null id AND if the last attempt didn't fail,
0070         // proceed with the credentials receieved from the SSO;
0071         // otherwise prompt the user
0072         if (!m_lastLoginFailedConfig.hasKey(m_account->objectPath())) {
0073             GetCredentialsJob *credentialsJob = new GetCredentialsJob(m_accountStorageId, QStringLiteral("password"), QStringLiteral("password"), this);
0074             connect(credentialsJob, &GetCredentialsJob::finished, [this](KJob *job){
0075                 if (job->error()) {
0076                     qWarning() << "Credentials job error:" << job->errorText();
0077                     qDebug() << "Prompting for password";
0078                     promptUser();
0079                 } else {
0080                     m_canFinish = true;
0081                     QByteArray secret = qobject_cast<GetCredentialsJob*>(job)->credentialsData().value("Secret").toByteArray();
0082                     m_saslIface->StartMechanismWithData(QLatin1String("X-TELEPATHY-PASSWORD"), secret);
0083                 }
0084             });
0085             credentialsJob->start();
0086         } else {
0087             promptUser();
0088         }
0089     } else if (status == Tp::SASLStatusServerSucceeded) {
0090         qDebug() << "Authentication handshake";
0091         m_saslIface->AcceptSASL();
0092     } else if (status == Tp::SASLStatusSucceeded) {
0093         qDebug() << "Authentication succeeded";
0094         if (m_lastLoginFailedConfig.hasKey(m_account->objectPath())) {
0095             m_lastLoginFailedConfig.deleteEntry(m_account->objectPath());
0096         }
0097         if (m_canFinish) {
0098             // if the credentials storage has finished, just finish
0099             setFinished();
0100         } else {
0101             // ...otherwise set this to true and it will finish
0102             // when credentials are finished
0103             m_canFinish = true;
0104         }
0105     } else if (status == Tp::SASLStatusInProgress) {
0106         qDebug() << "Authenticating...";
0107     } else if (status == Tp::SASLStatusServerFailed) {
0108         qDebug() << "Error authenticating - reason:" << reason << "- details:" << details;
0109 
0110         if (m_canTryAgain) {
0111             qDebug() << "Retrying...";
0112             promptUser();
0113         } else {
0114             qWarning() << "Authentication failed and cannot try again";
0115             m_lastLoginFailedConfig.writeEntry(m_account->objectPath(), "1");
0116 
0117             // We cannot try again, but we can request again to set the account
0118             // online. A new channel will be created, but since we set the
0119             // lastLoginFailed entry, next time we will prompt for password
0120             // and the user won't see any difference except for an
0121             // authentication error notification
0122             Tp::Presence requestedPresence = m_account->requestedPresence();
0123             m_account->setRequestedPresence(requestedPresence);
0124             QString errorMessage = details[QLatin1String("server-message")].toString();
0125             setFinishedWithError(reason, errorMessage.isEmpty() ? i18n("Authentication error") : errorMessage);
0126         }
0127     }
0128 }
0129 
0130 void XTelepathyPasswordAuthOperation::promptUser()
0131 {
0132     m_dialog = new XTelepathyPasswordPrompt(m_account);
0133     connect(m_dialog.data(),
0134             SIGNAL(finished(int)),
0135             SLOT(onDialogFinished(int)));
0136     m_dialog.data()->show();
0137 }
0138 
0139 void XTelepathyPasswordAuthOperation::onDialogFinished(int result)
0140 {
0141     switch (result) {
0142     case QDialog::Rejected:
0143         qDebug() << "Authentication cancelled";
0144         m_saslIface->AbortSASL(Tp::SASLAbortReasonUserAbort, i18n("User cancelled auth"));
0145         setFinished();
0146         if (!m_dialog.isNull()) {
0147             m_dialog.data()->deleteLater();
0148         }
0149         return;
0150     case QDialog::Accepted:
0151         // save password in kwallet if necessary...
0152         if (!m_dialog.isNull()) {
0153             if (m_dialog.data()->savePassword()) {
0154                 qDebug() << "Saving password in SSO";
0155                 m_canFinish = false;
0156                 storeCredentials(m_dialog.data()->password());
0157             } else {
0158                 m_canFinish = true;
0159             }
0160 
0161             m_dialog.data()->deleteLater();
0162 
0163             m_saslIface->StartMechanismWithData(QLatin1String("X-TELEPATHY-PASSWORD"), m_dialog.data()->password().toUtf8());
0164         }
0165     }
0166 }
0167 
0168 void XTelepathyPasswordAuthOperation::storeCredentials(const QString &secret)
0169 {
0170     QString username = m_account->parameters().value(QStringLiteral("account")).toString();
0171     Accounts::Manager *manager = KAccounts::accountsManager();
0172     Accounts::Account *account = manager->account(m_accountStorageId);
0173     SignOn::Identity *identity = nullptr;
0174 
0175     if (account) {
0176         Accounts::AccountService *service = new Accounts::AccountService(account, manager->service(QString()), this);
0177         Accounts::AuthData authData = service->authData();
0178         identity = SignOn::Identity::existingIdentity(authData.credentialsId(), this);
0179     } else {
0180         // there's no valid KAccounts account, so let's try creating one
0181         QString providerName = QStringLiteral("ktp-");
0182 
0183         providerName.append(m_account->serviceName());
0184 
0185         qDebug() << "Creating account with providerName" << providerName;
0186 
0187         account = manager->createAccount(providerName);
0188         account->setDisplayName(m_account->displayName());
0189         account->setValue("uid", m_account->objectPath());
0190         account->setValue("username", username);
0191         account->setValue(QStringLiteral("auth/mechanism"), QStringLiteral("password"));
0192         account->setValue(QStringLiteral("auth/method"), QStringLiteral("password"));
0193 
0194         account->setEnabled(true);
0195 
0196         Accounts::ServiceList services = account->services();
0197         Q_FOREACH(const Accounts::Service &service, services) {
0198             account->selectService(service);
0199             account->setEnabled(true);
0200         }
0201     }
0202 
0203     SignOn::IdentityInfo info;
0204     info.setUserName(username);
0205     info.setSecret(secret);
0206     info.setCaption(username);
0207     info.setAccessControlList(QStringList(QLatin1String("*")));
0208     info.setType(SignOn::IdentityInfo::Application);
0209 
0210     if (!identity) {
0211         // we don't have a valid SignOn::Identity, let's create new one
0212         identity = SignOn::Identity::newIdentity(info, this);
0213     }
0214 
0215     identity->storeCredentials(info);
0216 
0217     connect(identity, &SignOn::Identity::credentialsStored, [=](const quint32 id) {
0218         account->setCredentialsId(id);
0219         account->sync();
0220 
0221         connect(account, &Accounts::Account::synced, [=]() {
0222             m_accountStorageId = account->id();
0223 
0224             qDebug() << "Account credentials synchronisation finished";
0225 
0226             if (m_canFinish) {
0227                 // if the sasl channel has already finished, set finished
0228                 setFinished();
0229             } else {
0230                 // ...otherwise set this to true and when the auth succeeds,
0231                 // it will finish then
0232                 m_canFinish = true;
0233             }
0234         });
0235     });
0236 }