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 }