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 }