diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,8 @@ find_package(Intltool REQUIRED) find_package(KAccounts REQUIRED) -find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Core Qml) -find_package(KF5 ${KF5_MIN_VERSION} REQUIRED KIO - I18n - Declarative - Package) +find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Core Qml WebEngineWidgets) +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED KIO I18n Declarative Package) include(KDEInstallDirs) include(KDECMakeSettings) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(owncloud-ui) +add_subdirectory(nextcloud-ui) \ No newline at end of file diff --git a/plugins/nextcloud-ui/CMakeLists.txt b/plugins/nextcloud-ui/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/CMakeLists.txt @@ -0,0 +1,22 @@ +set(nextcloud_plugin_kaccounts_SRCS + nextcloud.cpp + qmlhelper.cpp +) + +add_library(nextcloud_plugin_kaccounts MODULE + ${nextcloud_plugin_kaccounts_SRCS} +) + +target_link_libraries(nextcloud_plugin_kaccounts + Qt5::Core + Qt5::WebEngineWidgets + KF5::KIOCore + KF5::I18n + KF5::Declarative + KAccounts +) + +install(TARGETS nextcloud_plugin_kaccounts + DESTINATION ${PLUGIN_INSTALL_DIR}/kaccounts/ui +) +kpackage_install_package(package org.kde.kaccounts.nextcloud genericqml) diff --git a/plugins/nextcloud-ui/nextcloud.h b/plugins/nextcloud-ui/nextcloud.h new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/nextcloud.h @@ -0,0 +1,55 @@ +/************************************************************************************* + * Copyright (C) 2019 by Rituka Patwal * + * Copyright (C) 2015 by Martin Klapetek * + * Copyright (C) 2012 by Alejandro Fiestas Olivares * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef NEXTCLOUD_H +#define NEXTCLOUD_H + +#include +#include + +#include + +namespace KDeclarative { + class QmlObject; +} + +class NextcloudWizard : public KAccountsUiPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.kaccounts.UiPlugin") + Q_INTERFACES(KAccountsUiPlugin) + +public: + explicit NextcloudWizard(QObject *parent = 0); + virtual ~NextcloudWizard(); + + virtual void init(KAccountsUiPlugin::UiType type) override; + virtual void setProviderName(const QString &providerName) override; + virtual void showNewAccountDialog() override; + virtual void showConfigureAccountDialog(const quint32 accountId) override; + virtual QStringList supportedServicesForConfig() const override; + +private: + QString m_providerName; + QHash m_services; + KDeclarative::QmlObject *m_object; +}; + +#endif //NEXTCLOUD_H diff --git a/plugins/nextcloud-ui/nextcloud.cpp b/plugins/nextcloud-ui/nextcloud.cpp new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/nextcloud.cpp @@ -0,0 +1,98 @@ +/************************************************************************************* + * Copyright (C) 2019 by Rituka Patwal * + * Copyright (C) 2015 by Martin Klapetek * + * Copyright (C) 2012 by Alejandro Fiestas Olivares * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "nextcloud.h" +#include "qmlhelper.h" + +#include +#include + +#include +#include + +NextcloudWizard::NextcloudWizard(QObject *parent) + : KAccountsUiPlugin(parent) +{ +} + +NextcloudWizard::~NextcloudWizard() +{ +} + +void NextcloudWizard::init(KAccountsUiPlugin::UiType type) +{ + if (type == KAccountsUiPlugin::NewAccountDialog) { + + const QString packagePath("org.kde.kaccounts.nextcloud"); + + m_object = new KDeclarative::QmlObject(); + m_object->setTranslationDomain(packagePath); + m_object->setInitializationDelayed(true); + m_object->loadPackage(packagePath); + + QmlHelper *helper = new QmlHelper(m_object); + connect(helper, &QmlHelper::wizardFinished, this, &NextcloudWizard::success); + connect(helper, &QmlHelper::wizardFinished, [=] { + QWindow *window = qobject_cast(m_object->rootObject()); + if (window) { + window->close(); + } + m_object->deleteLater(); + }); + m_object->engine()->rootContext()->setContextProperty("helper", helper); + + m_object->completeInitialization(); + + if (!m_object->package().metadata().isValid()) { + return; + } + + Q_EMIT uiReady(); + } + +} + +void NextcloudWizard::setProviderName(const QString &providerName) +{ + //TODO: unused? + m_providerName = providerName; +} + +void NextcloudWizard::showNewAccountDialog() +{ + QWindow *window = qobject_cast(m_object->rootObject()); + if (window) { + window->setTransientParent(transientParent()); + window->show(); + window->requestActivate(); + window->setTitle(m_object->package().metadata().name()); + window->setIcon(QIcon::fromTheme(m_object->package().metadata().iconName())); + } +} + +void NextcloudWizard::showConfigureAccountDialog(const quint32 accountId) +{ + +} + +QStringList NextcloudWizard::supportedServicesForConfig() const +{ + return QStringList(); +} diff --git a/plugins/nextcloud-ui/package/contents/ui/main.qml b/plugins/nextcloud-ui/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/package/contents/ui/main.qml @@ -0,0 +1,100 @@ +/* + * Copyright 2019 (C) Rituka Patwal + * Copyright 2015 (C) Martin Klapetek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.2 +import org.kde.kirigami 2.5 as Kirigami +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.5 + +Kirigami.ApplicationWindow { + id: ncAccountRoot + objectName: "_root" + + width: Kirigami.Units.gridUnit * 25 + height: Kirigami.Units.gridUnit * 15 + + pageStack.initialPage: mainPageComponent + + Component { + id: mainPageComponent + + Kirigami.Page { + title: i18n("Nextcloud Login") + + ColumnLayout { + spacing: 10 + anchors.fill: parent + + visible: !helper.isLoginComplete + + TextField { + id: serverText + placeholderText: qsTr("Server address") + Layout.fillWidth: true + } + Label { + id: errorLabel + Layout.fillWidth: true + visible: text.length > 0 + text: helper.errorMessage + wrapMode: Text.WordWrap + } + BusyIndicator { + id: busy + width: Kirigami.Units.gridUnit * 2 + height: width + Layout.alignment: Qt.AlignCenter + running: helper.isWorking + visible: running + } + + Button { + Layout.fillWidth: true + id: loginButton + icon.name: "go-next" + text: i18n("Next") + onClicked: helper.checkServer(serverText.text) + } + } + ColumnLayout { + id: services + visible: helper.isLoginComplete + + Label { + text: i18n("Choose services to enable"); + } + + CheckBox { + id: contactsService + text: i18n("Contacts") + } + Button { + id: finishButton + Layout.fillWidth: true + text: i18n("Finish") + + onClicked: { + helper.finish(contactsService.checked); + } + } + } + } + } +} diff --git a/plugins/nextcloud-ui/package/metadata.desktop b/plugins/nextcloud-ui/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/package/metadata.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Name=Nextcloud +Comment=Use Nextcloud with KAccounts +Encoding=UTF-8 +Type=Service +Icon=applications-internet +X-KDE-PluginInfo-Author=Rituka Patwal +X-KDE-PluginInfo-Email=ritukapatwal21@gmail.com +X-KDE-PluginInfo-Name=nextcloud_kaccounts_ui +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://kde.org +X-KDE-PluginInfo-Category=Network +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +X-KDE-FormFactors=handset,tablet,mediacenter +X-Plasma-MainScript=ui/main.qml diff --git a/plugins/nextcloud-ui/qmlhelper.h b/plugins/nextcloud-ui/qmlhelper.h new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/qmlhelper.h @@ -0,0 +1,91 @@ +/************************************************************************************* + * Copyright (C) 2019 by Rituka Patwal * + * Copyright (C) 2015 by Martin Klapetek * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#ifndef QMLHELPER_H +#define QMLHELPER_H + +#include +#include +#include +#include +#include +#include + +namespace KIO +{ + class Job; +}; + +class KJob; + +class QmlHelper : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool isWorking READ isWorking NOTIFY isWorkingChanged) + Q_PROPERTY(bool noError READ noError NOTIFY noErrorChanged) + Q_PROPERTY(bool isLoginComplete READ isLoginComplete NOTIFY isLoginCompleteChanged) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) + +public: + QmlHelper(QObject *parent = 0); + ~QmlHelper(); + + Q_INVOKABLE void checkServer(const QString &server); + Q_INVOKABLE void finish(bool contactsEnabled); + bool isWorking(); + bool noError(); + bool isLoginComplete(); + QString errorMessage() const; + +Q_SIGNALS: + void isWorkingChanged(); + void noErrorChanged(); + void errorMessageChanged(); + void isLoginCompleteChanged(); + void wizardFinished(const QString &username, const QString &password, const QVariantMap &data); + +private Q_SLOTS: + void fileChecked(KJob *job); + void dataReceived(KIO::Job *job, const QByteArray &data); + void authCheckResult(KJob *job); + void finalUrlHandler(const QUrl &url); + +private: + void checkServer(const QUrl &url); + void figureOutServer(const QUrl &url); + void setWorking(bool start); + void serverCheckResult(); + void openWebView(); + void wrongUrlDetected(); + + QByteArray m_json; + QString m_errorMessage; + QString m_server; + QString m_username; + QString m_password; + QUrl m_finalUrl; + QWebEngineView * m_view = new QWebEngineView; + QStringList m_disabledServices; + bool m_isWorking = false; + bool m_noError = false; + bool m_isLoginComplete = false; + +}; + +#endif // QMLHELPER_H diff --git a/plugins/nextcloud-ui/qmlhelper.cpp b/plugins/nextcloud-ui/qmlhelper.cpp new file mode 100644 --- /dev/null +++ b/plugins/nextcloud-ui/qmlhelper.cpp @@ -0,0 +1,265 @@ +/************************************************************************************* + * Copyright (C) 2019 by Rituka Patwal * + * Copyright (C) 2015 by Martin Klapetek * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#include "qmlhelper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QmlHelper::QmlHelper(QObject *parent) + : QObject(parent) +{ +} + +QmlHelper::~QmlHelper() +{ +} + +void QmlHelper::checkServer(const QString &path) +{ + m_errorMessage.clear(); + Q_EMIT errorMessageChanged(); + + m_json.clear(); + QString urlString = path; + + //To remove "/'s" from the end + while(urlString.endsWith("/")) { + urlString.remove(urlString.length() - 1, 1); + } + + QUrl url = QUrl::fromUserInput(urlString); + url.setPath(url.path() + '/' + "status.php"); + + if (url.host().isEmpty()) { + return; + } + + checkServer(url); +} + +//To check if url is correct +void QmlHelper::checkServer(const QUrl &url) +{ + setWorking(true); + KIO::TransferJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); + job->setUiDelegate(0); + connect(job, &KIO::DavJob::data, this, &QmlHelper::dataReceived); + connect(job, &KIO::DavJob::finished, this, &QmlHelper::fileChecked); +} + +void QmlHelper::dataReceived(KIO::Job *job, const QByteArray &data) +{ + Q_UNUSED(job); + m_json.append(data); +} + +void QmlHelper::fileChecked(KJob* job) +{ + KIO::TransferJob *kJob = qobject_cast(job); + if (kJob->error()) { + wrongUrlDetected(); + return; + } + + QJsonDocument parser = QJsonDocument::fromJson(m_json); + QJsonObject map = parser.object(); + if (!map.contains("version")) { + wrongUrlDetected(); + return; + } + + QUrl url = KIO::upUrl(kJob->url()); + m_server = url.toString(); + + // Call webview for login + openWebView(); +} + +// When url entered by user is wrong +void QmlHelper::wrongUrlDetected() +{ + m_noError = false; + Q_EMIT noErrorChanged(); + m_errorMessage = i18n("Unable to connect to Nextcloud at the given server URL. Please check the server URL."); + setWorking(false); + Q_EMIT errorMessageChanged(); +} + + +// Open Webview for nextcloud login. +// Document for login flow : https://docs.nextcloud.com/server/stable/developer_manual/client_apis/LoginFlow/index.html +void QmlHelper::openWebView() +{ + QWebEngineHttpRequest request; + // set proper headers + request.setUrl(QUrl::fromUserInput(m_server + "/index.php/login/flow")); + request.setHeader( + QByteArray::fromStdString("USER_AGENT"), + QByteArray::fromStdString("Mozilla/5.0 nextcloud-ui-plugin") + ); + request.setHeader( + QByteArray::fromStdString("OCS-APIREQUEST"), + QByteArray::fromStdString("true") + ); + + m_view->load(request); + + // Delete cookies because it is a one time webview + m_view->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + // To catch the url of scheme *nc* on the final login + QDesktopServices::setUrlHandler("nc", this, "finalUrlHandler"); + + m_view->show(); + m_view->resize(424, 650); +} + +void QmlHelper::finalUrlHandler(const QUrl &url){ + m_finalUrl = url; + m_view->close(); + delete m_view; + + // To fetch m_username and m_password from final url + QString finalURLtoString = m_finalUrl.toString(); + int username_ini_pos = finalURLtoString.indexOf("&user:") + 6; + int password_ini_pos = finalURLtoString.indexOf("&password:") + 10; + int username_size = password_ini_pos - username_ini_pos - 10; + QString username = finalURLtoString.mid(username_ini_pos, username_size); + QString password = finalURLtoString.mid(password_ini_pos); + // To replace %40 with @ + int position = username.indexOf("%40"); + username.replace(position, 3, "@"); + + m_username = username; + m_password = password; + + serverCheckResult(); +} + +void QmlHelper::setWorking(bool start) +{ + if (start == m_isWorking) { + return; + } + + m_isWorking = start; + Q_EMIT isWorkingChanged(); +} + +void QmlHelper::serverCheckResult() +{ + m_noError = true; + Q_EMIT noErrorChanged(); + m_errorMessage.clear(); + m_json.clear(); + + QUrl url(m_server); + url.setUserName(m_username); + url.setPassword(m_password); + url = url.adjusted(QUrl::StripTrailingSlash); + url.setPath(url.path() + '/' + "remote.php/webdav"); + // Send a basic PROPFIND command to test access + const QString requestStr = QStringLiteral( + "" + "" + "" + "" + ""); + + KIO::DavJob *job = KIO::davPropFind(url, QDomDocument(requestStr), "1", KIO::HideProgressInfo); + connect(job, &KIO::DavJob::finished, this, &QmlHelper::authCheckResult); + connect(job, &KIO::DavJob::data, this, &QmlHelper::dataReceived); + + QVariantMap metadata{{"cookies","none"}, {"no-cache",true}}; + + job->setMetaData(metadata); + job->setUiDelegate(0); + job->start(); + + Q_EMIT errorMessageChanged(); +} + +void QmlHelper::authCheckResult(KJob *job) +{ + KIO::DavJob *kJob = qobject_cast(job); + + if (kJob->isErrorPage()) { + m_errorMessage = i18n("Unable to authenticate using the provided username and password"); + } else { + m_errorMessage.clear(); + m_isLoginComplete = true; + Q_EMIT isLoginCompleteChanged(); + } + + Q_EMIT errorMessageChanged(); + + m_noError = !kJob->isErrorPage(); + Q_EMIT noErrorChanged(); + setWorking(false); +} + +bool QmlHelper::isWorking() +{ + return m_isWorking; +} + +bool QmlHelper::noError() +{ + return m_noError; +} + +QString QmlHelper::errorMessage() const +{ + return m_errorMessage; +} + +bool QmlHelper::isLoginComplete() +{ + return m_isLoginComplete; +} + +void QmlHelper::finish(bool contactsEnabled) +{ + QVariantMap data; + data.insert("server", m_server); + + QUrl serverUrl(m_server); + + data.insert("dav/host", serverUrl.host()); + data.insert("dav/storagePath", QStringLiteral("/remote.php/dav/files/%1").arg(m_username)); + data.insert("dav/contactsPath", QStringLiteral("/remote.php/dav/addressbooks/users/%1").arg(m_username)); + + if (!contactsEnabled) { + data.insert("__service/nextcloud-contacts", false); + } + + Q_EMIT wizardFinished(m_username, m_password, data); +} diff --git a/plugins/owncloud-ui/CMakeLists.txt b/plugins/owncloud-ui/CMakeLists.txt --- a/plugins/owncloud-ui/CMakeLists.txt +++ b/plugins/owncloud-ui/CMakeLists.txt @@ -1,19 +1,17 @@ -project (owncloud-ui-plugin) - -include_directories (${CMAKE_CURRENT_BUILD_DIR} +include_directories(${CMAKE_CURRENT_BUILD_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) -set (owncloud_plugin_kaccounts_SRCS +set(owncloud_plugin_kaccounts_SRCS owncloud.cpp qmlhelper.cpp ) -add_library (owncloud_plugin_kaccounts MODULE +add_library(owncloud_plugin_kaccounts MODULE ${owncloud_plugin_kaccounts_SRCS} ) -target_link_libraries (owncloud_plugin_kaccounts +target_link_libraries(owncloud_plugin_kaccounts Qt5::Core KF5::KIOCore KF5::I18n @@ -24,7 +22,7 @@ ) # Install: -install (TARGETS owncloud_plugin_kaccounts +install(TARGETS owncloud_plugin_kaccounts DESTINATION ${PLUGIN_INSTALL_DIR}/kaccounts/ui ) kpackage_install_package(package org.kde.kaccounts.owncloud genericqml) diff --git a/providers/nextcloud.provider.in b/providers/nextcloud.provider.in new file mode 100644 --- /dev/null +++ b/providers/nextcloud.provider.in @@ -0,0 +1,16 @@ + + + <_name>Nextcloud + Nextcloud + + <_description>Nextcloud account + kaccounts-providers + nextcloud_plugin_kaccounts + + + diff --git a/services/nextcloud-contacts.service.in b/services/nextcloud-contacts.service.in new file mode 100644 --- /dev/null +++ b/services/nextcloud-contacts.service.in @@ -0,0 +1,24 @@ + + + dav-contacts + <_name>Contacts + view-pim-contacts + nextcloud + kaccounts-providers + + + + diff --git a/services/nextcloud-storage.service.in b/services/nextcloud-storage.service.in new file mode 100644 --- /dev/null +++ b/services/nextcloud-storage.service.in @@ -0,0 +1,16 @@ + + + dav-storage + <_name>Storage + Nextcloud + nextcloud + kaccounts-providers + + + +