File indexing completed on 2024-05-05 16:58:23

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Rituka Patwal <ritukapatwal21@gmail.com>
0003  *  SPDX-FileCopyrightText: 2015 Martin Klapetek <mklapetek@kde.org>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "nextcloudcontroller.h"
0009 
0010 #include <KIO/DavJob>
0011 #include <KIO/Job>
0012 #include <KLocalizedString>
0013 #include <QDesktopServices>
0014 #include <QDomDocument>
0015 #include <QJsonDocument>
0016 #include <QJsonObject>
0017 #include <QUrlQuery>
0018 #include <kio/global.h>
0019 
0020 #include "../cloudurls.h"
0021 
0022 // Document for login flow :  https://docs.nextcloud.com/server/stable/developer_manual/client_apis/LoginFlow/index.html
0023 
0024 void NextcloudUrlIntercepter::interceptRequest(QWebEngineUrlRequestInfo &info)
0025 {
0026     info.setHttpHeader("OCS-APIREQUEST", "true");
0027 }
0028 
0029 NextcloudController::NextcloudController(QObject *parent)
0030     : QObject(parent)
0031     , m_webengineProfile(new QQuickWebEngineProfile(this))
0032 {
0033     m_webengineProfile->setUrlRequestInterceptor(&m_urlIntercepter);
0034     m_webengineProfile->setHttpUserAgent(QStringLiteral("KAccounts Nextcloud Login"));
0035 
0036     QDesktopServices::setUrlHandler(QStringLiteral("nc"), this, "finalUrlHandler");
0037 }
0038 
0039 NextcloudController::~NextcloudController()
0040 {
0041 }
0042 
0043 void NextcloudController::checkServer(const QString &path)
0044 {
0045     m_errorMessage.clear();
0046     Q_EMIT errorMessageChanged();
0047 
0048     m_json.clear();
0049 
0050     checkServer(createStatusUrl(path));
0051 }
0052 
0053 // To check if url is correct
0054 void NextcloudController::checkServer(const QUrl &url)
0055 {
0056     setWorking(true);
0057     KIO::TransferJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
0058     job->setUiDelegate(nullptr);
0059     connect(job, &KIO::DavJob::data, this, &NextcloudController::dataReceived);
0060     connect(job, &KIO::DavJob::finished, this, &NextcloudController::fileChecked);
0061 }
0062 
0063 void NextcloudController::dataReceived(KIO::Job *job, const QByteArray &data)
0064 {
0065     Q_UNUSED(job);
0066     m_json.append(data);
0067 }
0068 
0069 void NextcloudController::fileChecked(KJob *job)
0070 {
0071     KIO::TransferJob *kJob = qobject_cast<KIO::TransferJob *>(job);
0072     if (kJob->error()) {
0073         wrongUrlDetected();
0074         return;
0075     }
0076 
0077     QJsonDocument parser = QJsonDocument::fromJson(m_json);
0078     QJsonObject map = parser.object();
0079     if (!map.contains(QStringLiteral("version"))) {
0080         wrongUrlDetected();
0081         return;
0082     }
0083 
0084     QUrl url = KIO::upUrl(kJob->url());
0085     m_server = url.toString();
0086 
0087     m_loginUrl = m_server + QStringLiteral("/index.php/login/flow");
0088     Q_EMIT loginUrlChanged();
0089 
0090     m_state = WebLogin;
0091     Q_EMIT stateChanged();
0092 }
0093 
0094 // When url entered by user is wrong
0095 void NextcloudController::wrongUrlDetected()
0096 {
0097     m_errorMessage = i18n("Unable to connect to Nextcloud at the given server URL. Please check the server URL.");
0098     setWorking(false);
0099     Q_EMIT errorMessageChanged();
0100 }
0101 
0102 // Open Webview for nextcloud login.
0103 
0104 void NextcloudController::finalUrlHandler(const QUrl &url)
0105 {
0106     // url is of the form: nc://login/server:<server>&user:<loginname>&password:<password>
0107 
0108     QUrlQuery urlQuery;
0109     urlQuery.setQueryDelimiters(QLatin1Char(':'), QLatin1Char('&'));
0110     urlQuery.setQuery(url.path(QUrl::FullyEncoded).mid(1));
0111 
0112     m_username = urlQuery.queryItemValue(QStringLiteral("user"), QUrl::FullyDecoded);
0113     m_password = urlQuery.queryItemValue(QStringLiteral("password"), QUrl::FullyDecoded);
0114 
0115     serverCheckResult();
0116 }
0117 
0118 void NextcloudController::setWorking(bool start)
0119 {
0120     if (start == m_isWorking) {
0121         return;
0122     }
0123 
0124     m_isWorking = start;
0125     Q_EMIT isWorkingChanged();
0126 }
0127 
0128 void NextcloudController::serverCheckResult()
0129 {
0130     m_errorMessage.clear();
0131     m_json.clear();
0132 
0133     QUrl url(m_server);
0134     url.setUserName(m_username);
0135     url.setPassword(m_password);
0136     url = url.adjusted(QUrl::StripTrailingSlash);
0137     url.setPath(url.path() + QLatin1Char('/') + QLatin1String("remote.php/webdav"));
0138     // Send a basic PROPFIND command to test access
0139     const QString requestStr = QStringLiteral(
0140         "<d:propfind xmlns:d=\"DAV:\">"
0141         "<d:prop>"
0142         "<d:current-user-principal />"
0143         "</d:prop>"
0144         "</d:propfind>");
0145 
0146     KIO::DavJob *job = KIO::davPropFind(url, QDomDocument(requestStr).toString(), QStringLiteral("1"), KIO::HideProgressInfo);
0147     connect(job, &KIO::DavJob::finished, this, &NextcloudController::authCheckResult);
0148     connect(job, &KIO::DavJob::data, this, &NextcloudController::dataReceived);
0149 
0150     QVariantMap metadata{
0151         {QStringLiteral("cookies"), QStringLiteral("none")},
0152         {QStringLiteral("no-cache"), true},
0153     };
0154 
0155     job->setMetaData(metadata);
0156     job->setUiDelegate(nullptr);
0157     job->start();
0158 
0159     Q_EMIT errorMessageChanged();
0160 }
0161 
0162 void NextcloudController::authCheckResult(KJob *job)
0163 {
0164     KIO::DavJob *kJob = qobject_cast<KIO::DavJob *>(job);
0165 
0166     if (kJob->isErrorPage()) {
0167         m_errorMessage = i18n("Unable to authenticate using the provided username and password");
0168     } else {
0169         m_errorMessage.clear();
0170         m_state = Services;
0171         Q_EMIT stateChanged();
0172     }
0173 
0174     Q_EMIT errorMessageChanged();
0175 
0176     setWorking(false);
0177 }
0178 
0179 bool NextcloudController::isWorking()
0180 {
0181     return m_isWorking;
0182 }
0183 
0184 QString NextcloudController::errorMessage() const
0185 {
0186     return m_errorMessage;
0187 }
0188 
0189 void NextcloudController::cancel()
0190 {
0191     Q_EMIT wizardCancelled();
0192 }
0193 
0194 void NextcloudController::finish(const QStringList disabledServices)
0195 {
0196     QVariantMap data;
0197     data.insert(QStringLiteral("server"), m_server);
0198 
0199     QUrl serverUrl(m_server);
0200 
0201     QUrl carddavUrl(serverUrl.adjusted(QUrl::StripTrailingSlash));
0202     carddavUrl.setPath(carddavUrl.path() + QStringLiteral("/remote.php/carddav/addressbooks/%1").arg(m_username));
0203 
0204     QUrl webdavUrl(serverUrl.adjusted(QUrl::StripTrailingSlash));
0205     webdavUrl.setPath(webdavUrl.path() + QStringLiteral("/remote.php/dav/files/%1").arg(m_username));
0206 
0207     data.insert(QStringLiteral("dav/host"), serverUrl.host());
0208     data.insert(QStringLiteral("dav/storagePath"), webdavUrl.path());
0209     data.insert(QStringLiteral("dav/contactsPath"), carddavUrl.path());
0210 
0211     for (const QString &service : disabledServices) {
0212         data.insert(QStringLiteral("__service/") + service, false);
0213     }
0214 
0215     Q_EMIT wizardFinished(m_username, m_password, data);
0216 }
0217 
0218 QVariantList NextcloudController::availableServices() const
0219 {
0220     // TODO Find a way to not hardcode this
0221     return {QVariant::fromValue(Service{QStringLiteral("nextcloud-contacts"), i18n("Contacts"), i18n("Synchronize contacts")}),
0222             QVariant::fromValue(Service{QStringLiteral("nextcloud-storage"), i18n("Storage"), i18n("Integrate into file manager")})};
0223 }