diff --git a/CMakeLists.txt b/CMakeLists.txt index f46bca053..5aa5d61eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,156 +1,158 @@ cmake_minimum_required(VERSION 2.8.12) project(kdepim-runtime) ############### KDEPIM-Runtime version ################ # KDEPIM_RUNTIME_VERSION # Version scheme: "x.y.z build". # # x is the version number. # y is the major release number. # z is the minor release number. # # "x.y.z" follow the kdelibs version kdepim is released with. # # If "z" is 0, it the version is "x.y" # # KDEPIM_RUNTIME_DEV_VERSION # is empty for final versions. For development versions "build" is # something like "pre", "alpha1", "alpha2", "beta1", "beta2", "rc1", "rc2". # # Examples in chronological order: # # 3.0 # 3.0.1 # 3.1 alpha1 # 3.1 beta1 # 3.1 beta2 # 3.1 rc1 # 3.1 # 3.1.1 # 3.2 pre # 3.2 alpha1 if(NOT DEFINED KDEPIM_RUNTIME_DEV_VERSION) set(KDEPIM_RUNTIME_DEV_VERSION "pre") endif() set(KDEPIM_RUNTIME_VERSION_NUMBER "5.2.80") set(KDEPIM_RUNTIME_VERSION "${KDEPIM_RUNTIME_VERSION_NUMBER}${KDEPIM_RUNTIME_DEV_VERSION}") configure_file(kdepim-runtime-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdepim-runtime-version.h @ONLY) set(KF5_VERSION "5.23.0") find_package(ECM ${KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${kdepim-runtime_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMPackageConfigHelpers) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) set(QT_REQUIRED_VERSION "5.5.0") set(KDEPIMRUNTIME_LIB_VERSION "${KDEPIM_RUNTIME_VERSION_NUMBER}") set(KDEPIMRUNTIME_LIB_SOVERSION "5") set(AKONADI_VERSION "5.2.80") set(KCONTACTS_LIB_VERSION "5.2.80") set(KCALENDARCORE_LIB_VERSION "5.2.80") set(IDENTITYMANAGEMENT_LIB_VERSION "5.2.80") set(KMAILTRANSPORT_LIB_VERSION "5.2.80") set(CALENDARUTILS_LIB_VERSION "5.2.80") set(KIMAP_LIB_VERSION "5.2.80") set(KMBOX_LIB_VERSION "5.2.80") set(AKONADICALENDAR_LIB_VERSION "5.2.80") set(SYNDICATION_LIB_VERSION "5.2.80") set(KONTACTINTERFACE_LIB_VERSION "5.2.80") set(AKONADIKALARM_LIB_VERSION "5.2.80") set(KMIME_LIB_VERSION "5.2.80") set(XMLRPCCLIENT_LIB_VERSION "5.2.80") set(KCONTACTS_LIB_VERSION "5.2.80") set(AKONADIMIME_LIB_VERSION "5.2.80") set(AKONADICONTACT_LIB_VERSION "5.2.80") set(AKONADINOTE_LIB_VERSION "5.2.80") set(KPIMTEXTEDIT_LIB_VERSION "5.2.80") set( SHARED_MIME_INFO_MINIMUM_VERSION "0.40" ) find_package( SharedMimeInfo REQUIRED ) # QT5 package find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Network Widgets Test XmlPatterns DBus) +find_package(Qt5 OPTIONAL_COMPONENTS WebEngineWidgets REQUIRED) + find_package(Qt5 OPTIONAL_COMPONENTS TextToSpeech) if (NOT Qt5TextToSpeech_FOUND) message(STATUS "Qt5TextToSpeech not found, speech feature will be disabled") else() add_definitions(-DHAVE_TEXTTOSPEECH) endif() # KF5 package find_package(KF5KDELibs4Support ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5NotifyConfig ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Kross ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_VERSION} CONFIG REQUIRED) find_packagE(KF5TextWidgets ${KF5_VERSION} CONFIG REQUIRED) # for KPluralHandlingSpinBox # KdepimLibs package find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5MailTransport ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiContact ${AKONADICONTACT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Contacts ${KCONTACTS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AlarmCalendar ${AKONADIKALARM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mbox ${KMBOX_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IMAP ${KIMAP_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Syndication ${SYNDICATION_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiNotes ${AKONADINOTE_LIB_VERSION} CONFIG REQUIRED) option(KDEPIM_RUN_ISOLATED_TESTS "Run the isolated tests." FALSE) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) find_package(Sasl2) set_package_properties(Sasl2 PROPERTIES DESCRIPTION "The Cyrus-sasl library" URL "http://www.cyrussasl.org" TYPE OPTIONAL ) add_subdirectory(resources) add_subdirectory(agents) add_subdirectory(plugins) add_subdirectory(defaultsetup) add_subdirectory(kioslave) add_subdirectory(migration) add_subdirectory(doc) ## install the MIME type spec file for KDEPIM specific MIME types install(FILES kdepim-mime.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) update_xdg_mimetypes(${KDE_INSTALL_MIMEDIR}) install( FILES kdepim-runtime.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES ) diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index c7e343dee..08a3d1d2c 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -1,87 +1,86 @@ project(resources) set(AKONADICALENDAR_LIB_VERSION "4.78.0") set(KMBOX_LIB_VERSION "4.78.0") find_package(KF5AkonadiCalendar ${AKONADICALENDAR_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mbox ${KMBOX_LIB_VERSION} CONFIG REQUIRED) # Extra package find_package(KF5GAPI "5.1.0" CONFIG) # Xsltproc find_package(Xsltproc) set_package_properties(Xsltproc PROPERTIES DESCRIPTION "XSLT processor from libxslt" TYPE REQUIRED PURPOSE "Required to generate D-Bus interfaces for all Akonadi resources.") # Libkolab find_package(Libkolab 1.0 QUIET CONFIG) set_package_properties(Libkolab PROPERTIES DESCRIPTION "libkolab" URL "http://mirror.kolabsys.com/pub/releases" TYPE OPTIONAL PURPOSE "The Kolab Format libraries are required to build the Kolab Groupware Resource") # Libkolabxml find_package(Libkolabxml 1.1 QUIET CONFIG) set_package_properties(Libkolabxml PROPERTIES DESCRIPTION "Kolabxml" URL "http://mirror.kolabsys.com/pub/releases" TYPE OPTIONAL PURPOSE "The Kolab XML Format Schema Definitions Library is required to build the Kolab Groupware Resource") include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/shared/singlefileresource/ ${CMAKE_CURRENT_BINARY_DIR}/shared/singlefileresource/ ${CMAKE_CURRENT_SOURCE_DIR}/folderarchivesettings/ ) # resource tests macro( akonadi_add_resourcetest _testname _script ) if ( ${EXECUTABLE_OUTPUT_PATH} ) set( _exepath ${EXECUTABLE_OUTPUT_PATH} ) else () set( _exepath ${kdepim-runtime_BINARY_DIR}/resourcetester ) endif () if (WIN32) set(_resourcetester ${_exepath}/resourcetester.bat) else () set(_resourcetester ${_exepath}/resourcetester) endif () if (UNIX) set(_resourcetester ${_resourcetester}.shell) endif () configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${_script} ${CMAKE_CURRENT_BINARY_DIR}/${_script} COPYONLY) if (KDEPIM_RUN_ISOLATED_TESTS) add_test( akonadi-mysql-db-${_testname} akonaditest -c ${kdepim-runtime_SOURCE_DIR}/resourcetester/tests/unittestenv/config-mysql-db.xml ${_resourcetester} -c ${CMAKE_CURRENT_BINARY_DIR}/${_script} ) endif () endmacro( akonadi_add_resourcetest ) add_subdirectory( akonotes ) add_subdirectory( kalarm ) add_subdirectory( contacts ) add_subdirectory( dav ) add_subdirectory( ical ) add_subdirectory( imap ) if (Libkolab_FOUND AND Libkolabxml_FOUND) add_subdirectory( kolab ) endif() add_subdirectory( maildir ) add_subdirectory( openxchange ) add_subdirectory( pop3 ) if( KF5GAPI_FOUND ) add_subdirectory( google ) # Disabled in KDE 4.14 - too many issues for stable release #add_subdirectory( gmail ) endif() add_subdirectory( shared ) add_subdirectory( birthdays ) add_subdirectory( mixedmaildir ) add_subdirectory( mbox ) add_subdirectory( vcarddir ) add_subdirectory( icaldir ) add_subdirectory( vcard ) add_subdirectory( folderarchivesettings ) - - +add_subdirectory( tomboynotes ) diff --git a/resources/tomboynotes/CMakeLists.txt b/resources/tomboynotes/CMakeLists.txt new file mode 100644 index 000000000..128ac8bc1 --- /dev/null +++ b/resources/tomboynotes/CMakeLists.txt @@ -0,0 +1,83 @@ +remove_definitions(-DQT_NO_URL_CAST_FROM_STRING + -DQT_NO_CAST_FROM_BYTEARRAY + -DQT_NO_CAST_FROM_ASCII + -DQT_NO_CAST_TO_ASCII + -DQT_USE_QSTRINGBUILDER + -DQT_USE_FAST_OPERATOR_PLUS +) + + +########### next target ############### + +set(tomboynotesresource_SRCS + configdialog.cpp + tomboynotesresource.cpp + tomboycollectionsdownloadjob.cpp + tomboyitemdownloadjob.cpp + tomboyitemuploadjob.cpp + tomboyitemsdownloadjob.cpp + tomboyjobbase.cpp + tomboyserverauthenticatejob.cpp + o1tomboy.cpp + o2/o0settingsstore.cpp + o2/o0baseauth.cpp + o2/o0abstractstore.h + o2/o0globals.h + o2/o1.cpp + o2/o1requestor.cpp + o2/o1timedreply.cpp + o2/o2.cpp + o2/o2reply.cpp + o2/o2replyserver.cpp + o2/o2requestor.cpp + o2/o2simplecrypt.cpp +) + +ecm_qt_declare_logging_category(tomboynotesresource_SRCS + HEADER debug.h + IDENTIFIER log_tomboynotesresource + CATEGORY_NAME log_tomboynotesresource +) + +ki18n_wrap_ui(tomboynotesresource_SRCS configdialog.ui) + +kconfig_add_kcfg_files(tomboynotesresource_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/settings.kcfgc +) + +kcfg_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/tomboynotesresource.kcfg + org.kde.Akonadi.TomboyNotes.Settings +) + +qt5_add_dbus_adaptor(tomboynotesresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.TomboyNotes.Settings.xml + ${CMAKE_CURRENT_BINARY_DIR}/settings.h + Settings +) + + +add_executable(akonadi_tomboynotes_resource ${tomboynotesresource_SRCS}) +target_link_libraries(akonadi_tomboynotes_resource + Qt5::DBus + Qt5::Gui + Qt5::Network + Qt5::WebEngineWidgets + KF5::AkonadiAgentBase + KF5::AkonadiNotes + KF5::ConfigCore + KF5::ConfigWidgets + KF5::I18n + KF5::KIOCore + KF5::KIOFileWidgets + KF5::KIONTLM + KF5::KIOWidgets + KF5::Mime + KF5::WindowSystem +) + +install(TARGETS akonadi_tomboynotes_resource ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +install(FILES tomboynotesresource.desktop + DESTINATION "${KDE_INSTALL_DATAROOTDIR}/share/akonadi/agents" +) diff --git a/resources/tomboynotes/Messages.sh b/resources/tomboynotes/Messages.sh new file mode 100644 index 000000000..8f7d490a3 --- /dev/null +++ b/resources/tomboynotes/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` `find . -name \*.kcfg` >> rc.cpp || exit 11 +$XGETTEXT *.cpp -o $podir/akonadi_tomboynotes_resource.pot +rm -f rc.cpp diff --git a/resources/tomboynotes/README b/resources/tomboynotes/README new file mode 100644 index 000000000..dd3f98f67 --- /dev/null +++ b/resources/tomboynotes/README @@ -0,0 +1,24 @@ +==== What's this ? ==== +This is an Akonadi resource for Tomboy-compatible note servers like Grauphel. + +==== Usage ==== +For this resource you need only the URL of your service, +e.g. https://fqdn/index.php/apps/grauphel +It is the same URL that you have used in Tomboy. +For the registration process it will open a web page where you can log in +with your username and password of your account of the note server. + +==== Tested with ==== +Today it's tested only with Grauphel. So it may be incompatible with +other server implemantations (e.g. Rainy). But you are free to test it +and to report your experiences. + +==== Depends on ==== +the OAuth library o2. At the moment it is shipped with the resource code +in the subfolder o2. You can find the licence in the file o2/LICENSE. + +The shipped version is based on this last commit: +https://github.com/pipacs/o2/commit/0d39396e06fc54dfef9433497f78567944519602 + +I changed only the o2 includes and the code style. The changes are documented +in o2/changes.patch. diff --git a/resources/tomboynotes/configdialog.cpp b/resources/tomboynotes/configdialog.cpp new file mode 100644 index 000000000..5e8c911ec --- /dev/null +++ b/resources/tomboynotes/configdialog.cpp @@ -0,0 +1,80 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "configdialog.h" +#include "ui_configdialog.h" +#include "settings.h" +#include +#include + +ConfigDialog::ConfigDialog(Settings *settings, QWidget *parent) : + QDialog(parent), + ui(new Ui::ConfigDialog), + mSettings(settings) +{ + // Create window + setWindowTitle(i18n("Select a Tomboy server")); + QWidget *mainWidget = new QWidget(this); + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout(mainLayout); + mainLayout->addWidget(mainWidget); + ui->setupUi(mainWidget); + + // KSettings stuff. Works not in the initialization list! + mManager = new KConfigDialogManager(this, settings); + mManager->updateWidgets(); + + // Set the button actions + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ConfigDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ConfigDialog::reject); + + // Load config dialog window size settings + KConfigGroup group(KSharedConfig::openConfig(), "ConfigDialog"); + const QSize size = group.readEntry("Size", QSize(600, 400)); + if (size.isValid()) { + resize(size); + } + + ui->kcfg_ServerURL->setReadOnly(!mSettings->requestToken().isEmpty()); +} + +ConfigDialog::~ConfigDialog() +{ + // Save config dialog window size + KConfigGroup group(KSharedConfig::openConfig(), "ConfigDialog"); + group.writeEntry("Size", size()); + group.sync(); + + delete ui; +} + +void ConfigDialog::saveSettings() +{ + if (ui->kcfg_ServerURL->text() != mSettings->serverURL()) { + mSettings->setRequestToken(QString()); + mSettings->setRequestTokenSecret(QString()); + } + + if (ui->kcfg_collectionName->text().isEmpty()) { + ui->kcfg_collectionName->setText(i18n("Tomboy Notes %1", Settings::serverURL())); + } + + mManager->updateSettings(); + mSettings->save(); +} diff --git a/resources/tomboynotes/configdialog.h b/resources/tomboynotes/configdialog.h new file mode 100644 index 000000000..ec78ccc85 --- /dev/null +++ b/resources/tomboynotes/configdialog.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef CONFIGDIALOG_H +#define CONFIGDIALOG_H + +#include + +class KConfigDialogManager; + +class Settings; + +namespace Ui +{ +class ConfigDialog; +} + +class ConfigDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ConfigDialog(Settings *settings, QWidget *parent = Q_NULLPTR); + ~ConfigDialog(); + + void saveSettings(); + +private: + Ui::ConfigDialog *ui; + + KConfigDialogManager *mManager; + Settings *mSettings; +}; + +#endif // CONFIGDIALOG_H diff --git a/resources/tomboynotes/configdialog.ui b/resources/tomboynotes/configdialog.ui new file mode 100644 index 000000000..83b98181a --- /dev/null +++ b/resources/tomboynotes/configdialog.ui @@ -0,0 +1,130 @@ + + + Till Adam <adam@kde.org> + ConfigDialog + + + + 0 + 0 + 400 + 290 + + + + Tomboy Server Settings + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Tomboy + + + + + + Display name: + + + + + + + true + + + + + + + Type in the server URL: + + + + + + + This value is not changeable after first setup + + + true + + + + + + + Open in read-only mode + + + + + + + Ignore SSL errors + + + + + + + Qt::Vertical + + + + 20 + 141 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + true + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ + +
diff --git a/resources/tomboynotes/o1tomboy.cpp b/resources/tomboynotes/o1tomboy.cpp new file mode 100644 index 000000000..fe3cb6b46 --- /dev/null +++ b/resources/tomboynotes/o1tomboy.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "o1tomboy.h" + +O1Tomboy::O1Tomboy(QObject *parent) : O1(parent) +{ +} + +void O1Tomboy::setBaseURL(const QString &value) +{ + setRequestTokenUrl(QUrl(value + QStringLiteral("/oauth/request_token"))); + setAuthorizeUrl(QUrl(value + QStringLiteral("/oauth/authorize"))); + setAccessTokenUrl(QUrl(value + QStringLiteral("/oauth/access_token"))); + setClientId("anyone"); + setClientSecret("anyone"); +} + +QString O1Tomboy::getRequestToken() const +{ + return requestToken_; +} + +QString O1Tomboy::getRequestTokenSecret() const +{ + return requestTokenSecret_; +} + +void O1Tomboy::restoreAuthData(const QString &token, const QString &secret) +{ + requestToken_ = token; + requestTokenSecret_ = secret; + setLinked(true); +} diff --git a/resources/tomboynotes/o1tomboy.h b/resources/tomboynotes/o1tomboy.h new file mode 100644 index 000000000..8d7b7264e --- /dev/null +++ b/resources/tomboynotes/o1tomboy.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef O1TOMBOY_H +#define O1TOMBOY_H + +#include "o2/o1.h" + +class O1Tomboy : public O1 +{ + Q_OBJECT +public: + explicit O1Tomboy(QObject *parent = Q_NULLPTR); + + void setBaseURL(const QString &value); + QString getRequestToken() const; + + QString getRequestTokenSecret() const; + + void restoreAuthData(const QString &token, const QString &secret); +}; + +#endif // O1TOMBOY_H diff --git a/resources/tomboynotes/o2/CHANGES.md b/resources/tomboynotes/o2/CHANGES.md new file mode 100644 index 000000000..601ed4e9a --- /dev/null +++ b/resources/tomboynotes/o2/CHANGES.md @@ -0,0 +1,16 @@ +# Change Log + +## 1.0.0 + +* O1 and O2 to share a common base class +* Move common classes to the O0 name space +* Cleanups and bug fixes + +## 0.1.0 + +* Add Sialis, a Qt Quick Twitter demo client +* Cleanups and bug fixes + +## Pre-0.1.0 + +* Initial non-release diff --git a/resources/tomboynotes/o2/LICENSE b/resources/tomboynotes/o2/LICENSE new file mode 100644 index 000000000..9ac8d42fb --- /dev/null +++ b/resources/tomboynotes/o2/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2012, Akos Polster +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/resources/tomboynotes/o2/README.md b/resources/tomboynotes/o2/README.md new file mode 100644 index 000000000..bafc14b12 --- /dev/null +++ b/resources/tomboynotes/o2/README.md @@ -0,0 +1,212 @@ +# OAuth 1.0 and 2.0 for Qt + +This library encapsulates the OAuth 1.0 and 2.0 client authentication flows, and the sending of authenticated HTTP requests. + +The primary target is Qt Quick applications on embedded devices. + +Notes to contributors: + + * Please follow the coding style of the existing source + * Code contributions are released under Simplified BSD License, as specified in LICENSE. Do not contribute if this license does not suit your code + +## Classes + +Class | Header | Purpose +:-- | :-- | :-- +O0AbstractStore | o0abstractstore.h | Base class of persistent stores +O0BaseAuth | o0baseauth.h | Base class of OAuth authenticators +O0SettingsStore | o2settingsstore.h | QSettings-based persistent store +O0SimpleCrypt | o0simplecrypt.h | Simple encryption and decryption by Andre Somers +O1 | o1.h | Generic OAuth 1.0 authenticator +O1Dropbox | o1dropbox.h | Dropbox OAuth specialization +O1Flickr | o1flickr.h | Flickr OAuth specialization +O1Freshbooks | o1freshbooks.h | Freshbooks OAuth specialization +O1Requestor | o1requestor.h | Makes authenticated OAuth 1.0 requests: GET, POST or PUT, handles timeouts +O1RequestParameter | o1.h | An extra request parameter participating in request signing +O1Twitter | o1twitter.h | Twitter OAuth specialization +O2 | o2.h | Generic OAuth 2.0 authenticator +O2Facebook | o2facebook.h | Facebook OAuth specialization +O2Gft | o2gft.h | Google Fusion Tables OAuth specialization +O2Hubic | o2hubic.h | Hubic OAuth specialization +O2Reply | o2reply.h | A network request/reply that can time out +O2ReplyServer | o2replyserver.h | HTTP server to process authentication responses +O2Requestor | o2requestor.h | Makes authenticated OAuth 2.0 requests (GET, POST or PUT), handles timeouts and token expiry +O2Skydrive | o2skydrive.h | OneDrive OAuth specialization +O2SurveyMonkey | o2surveymonkey.h | SurveyMonkey OAuth specialization +OXTwitter | oxtwitter.h | Twitter XAuth specialization + +## Installation + +Clone the Github repository, then add all files in *src* to your Qt project, by including *src/src.pri*. + +## Basic Usage + +This example assumes a hypothetical Twitter client that will post tweets. Twitter is using OAuth 1.0. + +### Setup + +Include the required header files, and have some member variables that will be used for authentication and sending requests: + + #include "o1twitter.h" + #include "o1requestor.h" + O1Twitter *o1; + +### Initialization + +Instantiate one of the authenticator classes, like O1Twitter, set your application ID and application secret, and install the signal handlers: + + o1 = new O1Twitter(this); + o1->setClientId(MY_CLIENT_ID); + o1->setClientSecret(MY_CLIENT_SECRET); + connect(o1, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o1, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(o1, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o1, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o1, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); + +**Note:** For browserless Twitter authentication, you can use the OXTwitter specialized class which can do Twitter XAuth. You will need to additionally provide your Twitter login credentials (username & password) before calling *link()*. + +### Handling Signals + +O2 is an asynchronous library. It will send signals at various stages of authentication and request processing. + +To handle these signals, implement the following slots in your code: + + void onLinkedChanged() { + // Linking (login) state has changed. + // Use o1->linked() to get the actual state + } + + void onLinkingFailed() { + // Login has failed + } + + void onLinkingSucceeded() { + // Login has succeeded + } + + void onOpenBrowser(const QUrl *url) { + // Open a web browser or a web view with the given URL. + // The user will interact with this browser window to + // enter login name, password, and authorize your application + // to access the Twitter account + } + + void onCloseBrowser() { + // Close the browser window opened in openBrowser() + } + +### Logging In + +To log in (e.g. to link your application to the OAuth service), call the link() method: + + o1->link(); + +This initiates the authentication sequence. Your signal handlers above will be called at various stages. Lastly, if linking succeeds, onLinkingSucceeded() will be called. + +### Logging Out + +To log out, call the unlink() method: + + o1->unlink(); + +Logging out always succeeds, and requires no user interaction. + +### Sending Authenticated Requests + +Once linked, you can start sending authenticated requests to the service. We start with a simple example of sending a text-only tweet or as it's known in Twitter docs, a 'status update'. + +First we need a Qt network manager and an O1 requestor object: + + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + O1Requestor *requestor = new O1Requestor(manager, o1, this); + +Next, create parameters for posting the update: + + QByteArray paramName("status"); + QByteArray tweetText("My first tweet!"); + + QList requestParams = QList(); + requestParams << O1RequestParameter(paramName, tweetText); + + QByteArray postData = O1::createQueryParams(requestParams); + + // Using Twitter's REST API ver 1.1 + QUrl url = QUrl("https://api.twitter.com/1.1/statuses/update.json"); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + +Finally we authenticate and send the request using the O1 requestor object: + + QNetworkReply *reply = requestor->post(request, reqestParams, postData); + +Continuing with the example, we will now send a tweet containing an image as well as a message. + +We create an HTTP request containing the image and the message, in the format specified by Twitter: + + QString imagePath("/tmp/image.jpg"); + QString message("My tweet with an image!"); + + QFileInfo fileInfo(imagePath); + QFile file(imagePath); + file.open(QIODevice::ReadOnly); + + QString boundary("7d44e178b0439"); + QByteArray data(QString("--" + boundary + "\r\n").toAscii()); + data += "Content-Disposition: form-data; name=\"media[]\"; filename=\"" + fileInfo.fileName() + "\"\r\n"; + data += "Content-Transfer-Encoding: binary\r\n"; + data += "Content-Type: application/octet-stream\r\n\r\n"; + data += file.readAll(); + file.close(); + data += QString("\r\n--") + boundary + "\r\n"; + data += "Content-Disposition: form-data; name=\"status\"\r\n"; + data += "Content-Transfer-Encoding: binary\r\n"; + data += "Content-Type: text/plain; charset=utf-8\r\n\r\n"; + data += message.toUtf8(); + data += QString("\r\n--") + boundary + "--\r\n"; + + QNetworkRequest request; + // Using Twitter's REST API ver 1.1 + request.setUrl(QUrl("https://api.twitter.com/1.1/statuses/update_with_media.json")); + request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); + request.setHeader(QNetworkRequest::ContentLengthHeader, data.length()); + + QNetworkReply *reply = requestor->post(request, QList(), data); + +That's it. Tweets using the O2 library! + +### Storing OAuth Tokens + +O2 provides simple storage classes for writing OAuth tokens in a peristent location. Currently, a QSettings based backing store **O2SettingsStore** is provided in O2. O2SettingsStore keeps all token values in an encrypted form. You have to specify the encryption key to use while constructing the object: + + O0SettingsStore settings = new O0SettingsStore("myencryptionkey"); + // Set the store before starting OAuth, i.e before calling link() + o1->setStore(settings); + ... + +You can also create it with your customized QSettings object. O2SettingsStore will then use that QSettings object for storing the tokens: + + O0SettingsStore settings = new O0SettingsStore(mySettingsObject, "myencryptionkey"); + +Once set, O2SettingsStore takes ownership of the QSettings object. + +**Note:** If you do not specify a storage object to use, O2 will create one by default (which QSettings based), and use it. In such a case, a default encryption key is used for encrypting the data. + +### Extra OAuth Tokens + +Some OAuth service providers provide additional information in the access token response. Eg: Twitter returns 2 additional tokens in it's access token response - *screen_name* and *user_id*. + +O2 provides all such tokens via the property - *extraTokens*. You can query this property after a successful OAuth exchange, i.e after the *linkingSucceeded()* signal has been emitted. + +## More Examples + +The *examples* folder contains complete example applications: + +Name | Description +:-- | :-- +facebookdemo | Command line application authenticating with Facebook +sialis | QT Quick Twitter client using OAuth 1 +twitterdemo | Command line client for authenticating with Twitter and posting status updates. Uses OAuth 1 or Twitter XAuth + + diff --git a/resources/tomboynotes/o2/acknowledgements.md b/resources/tomboynotes/o2/acknowledgements.md new file mode 100644 index 000000000..fe99b6c62 --- /dev/null +++ b/resources/tomboynotes/o2/acknowledgements.md @@ -0,0 +1,57 @@ +# SimpleCrypt by Andre Somers + +Cryptographic methods for Qt. + +> Copyright (c) 2011, Andre Somers +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> * Redistributions in binary form must reproduce the above copyright +> notice, this list of conditions and the following disclaimer in the +> documentation and/or other materials provided with the distribution. +> * Neither the name of the Rathenau Instituut, Andre Somers nor the +> names of its contributors may be used to endorse or promote products +> derived from this software without specific prior written permission. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +> ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +> WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY +> DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +> ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +> SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Mandeep Sandhu + +Configurable settings storage, Twitter XAuth specialization, new demos, cleanups. + +> "Hi Akos, +> +> I'm writing this mail to confirm that my contributions to the O2 library, available here https://github.com/pipacs/o2, can be freely distributed according to the project's license (as shown in the LICENSE file). +> +> Regards, +> -mandeep" + +# Sergey Gavrushkin + +FreshBooks specialization + +# Theofilos Intzoglou + +Hubic specialization + +# Dimitar + +SurveyMonkey specialization + +# David Brooks + +CMake related fixes and improvements. + diff --git a/resources/tomboynotes/o2/changes.patch b/resources/tomboynotes/o2/changes.patch new file mode 100644 index 000000000..af5f88218 --- /dev/null +++ b/resources/tomboynotes/o2/changes.patch @@ -0,0 +1,1612 @@ +diff --git a/src/o0abstractstore.h b/src/o0abstractstore.h +index 1397361..0464325 100644 +--- a/src/o0abstractstore.h ++++ b/src/o0abstractstore.h +@@ -5,11 +5,13 @@ + #include + + /// Storage for strings. +-class O0AbstractStore: public QObject { ++class O0AbstractStore: public QObject ++{ + Q_OBJECT + + public: +- explicit O0AbstractStore(QObject *parent = 0): QObject(parent) { ++ explicit O0AbstractStore(QObject *parent = 0): QObject(parent) ++ { + } + + /// Retrieve a string value by key. +diff --git a/src/o0baseauth.cpp b/src/o0baseauth.cpp +index d4f052d..c021776 100644 +--- a/src/o0baseauth.cpp ++++ b/src/o0baseauth.cpp +@@ -1,18 +1,20 @@ + #include + #include + +-#include "o0baseauth.h" +-#include "o0globals.h" +-#include "o0settingsstore.h" ++#include "o2/o0baseauth.h" ++#include "o2/o0globals.h" ++#include "o2/o0settingsstore.h" + + static const quint16 DefaultLocalPort = 1965; + +-O0BaseAuth::O0BaseAuth(QObject *parent): QObject(parent) { ++O0BaseAuth::O0BaseAuth(QObject *parent): QObject(parent) ++{ + localPort_ = DefaultLocalPort; + store_ = new O0SettingsStore(O2_ENCRYPTION_KEY, this); + } + +-void O0BaseAuth::setStore(O0AbstractStore *store) { ++void O0BaseAuth::setStore(O0AbstractStore *store) ++{ + if (store_) { + store_->deleteLater(); + } +@@ -25,74 +27,87 @@ void O0BaseAuth::setStore(O0AbstractStore *store) { + } + } + +-bool O0BaseAuth::linked() { ++bool O0BaseAuth::linked() ++{ + QString key = QString(O2_KEY_LINKED).arg(clientId_); + bool result = !store_->value(key).isEmpty(); +- qDebug() << "O0BaseAuth::linked:" << (result? "Yes": "No"); ++ qDebug() << "O0BaseAuth::linked:" << (result ? "Yes" : "No"); + return result; + } + +-void O0BaseAuth::setLinked(bool v) { +- qDebug() << "O0BaseAuth::setLinked:" << (v? "true": "false"); ++void O0BaseAuth::setLinked(bool v) ++{ ++ qDebug() << "O0BaseAuth::setLinked:" << (v ? "true" : "false"); + bool oldValue = linked(); + QString key = QString(O2_KEY_LINKED).arg(clientId_); +- store_->setValue(key, v? "1": ""); ++ store_->setValue(key, v ? "1" : ""); + if (oldValue != v) { + Q_EMIT linkedChanged(); + } + } + +-QString O0BaseAuth::tokenSecret() { ++QString O0BaseAuth::tokenSecret() ++{ + QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); + return store_->value(key); + } + +-void O0BaseAuth::setTokenSecret(const QString &v) { ++void O0BaseAuth::setTokenSecret(const QString &v) ++{ + QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); + store_->setValue(key, v); + Q_EMIT tokenSecretChanged(); + } + +-QString O0BaseAuth::token() { ++QString O0BaseAuth::token() ++{ + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + return store_->value(key); + } + +-void O0BaseAuth::setToken(const QString &v) { ++void O0BaseAuth::setToken(const QString &v) ++{ + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + store_->setValue(key, v); + Q_EMIT tokenChanged(); + } + +-QString O0BaseAuth::clientId() { ++QString O0BaseAuth::clientId() ++{ + return clientId_; + } + +-void O0BaseAuth::setClientId(const QString &value) { ++void O0BaseAuth::setClientId(const QString &value) ++{ + clientId_ = value; + Q_EMIT clientIdChanged(); + } + +-QString O0BaseAuth::clientSecret() { ++QString O0BaseAuth::clientSecret() ++{ + return clientSecret_; + } + +-void O0BaseAuth::setClientSecret(const QString &value) { ++void O0BaseAuth::setClientSecret(const QString &value) ++{ + clientSecret_ = value; + Q_EMIT clientSecretChanged(); + } + +-int O0BaseAuth::localPort() { ++int O0BaseAuth::localPort() ++{ + return localPort_; + } + +-void O0BaseAuth::setLocalPort(int value) { ++void O0BaseAuth::setLocalPort(int value) ++{ + qDebug() << "O0BaseAuth::setLocalPort:" << value; + localPort_ = value; + Q_EMIT localPortChanged(); + } + +-QVariantMap O0BaseAuth::extraTokens() { ++QVariantMap O0BaseAuth::extraTokens() ++{ + QString key = QString(O2_KEY_EXTRA_TOKENS).arg(clientId_); + QString value = store_->value(key); + QByteArray bytes = QByteArray::fromBase64(value.toLatin1()); +@@ -101,7 +116,8 @@ QVariantMap O0BaseAuth::extraTokens() { + return extraTokens_; + } + +-void O0BaseAuth::setExtraTokens(QVariantMap extraTokens) { ++void O0BaseAuth::setExtraTokens(QVariantMap extraTokens) ++{ + extraTokens_ = extraTokens; + QByteArray bytes; + QDataStream stream(&bytes, QIODevice::WriteOnly); +@@ -111,7 +127,8 @@ void O0BaseAuth::setExtraTokens(QVariantMap extraTokens) { + Q_EMIT extraTokensChanged(); + } + +-QByteArray O0BaseAuth::createQueryParameters(const QList ¶meters) { ++QByteArray O0BaseAuth::createQueryParameters(const QList ¶meters) ++{ + QByteArray ret; + bool first = true; + foreach (O0RequestParameter h, parameters) { +diff --git a/src/o0baseauth.h b/src/o0baseauth.h +index d8a68a2..c906edf 100644 +--- a/src/o0baseauth.h ++++ b/src/o0baseauth.h +@@ -8,11 +8,12 @@ + #include + #include + +-#include "o0abstractstore.h" +-#include "o0requestparameter.h" ++#include "o2/o0abstractstore.h" ++#include "o2/o0requestparameter.h" + + /// Base class of OAuth authenticators +-class O0BaseAuth : public QObject { ++class O0BaseAuth : public QObject ++{ + Q_OBJECT + + public: +diff --git a/src/o0requestparameter.h b/src/o0requestparameter.h +index c793aad..c966987 100644 +--- a/src/o0requestparameter.h ++++ b/src/o0requestparameter.h +@@ -4,8 +4,9 @@ + /// Request parameter (name-value pair) participating in authentication. + struct O0RequestParameter { + O0RequestParameter(const QByteArray &n, const QByteArray &v): name(n), value(v) {} +- bool operator <(const O0RequestParameter &other) const { +- return (name == other.name)? (value < other.value): (name < other.name); ++ bool operator <(const O0RequestParameter &other) const ++ { ++ return (name == other.name) ? (value < other.value) : (name < other.name); + } + QByteArray name; + QByteArray value; +diff --git a/src/o0settingsstore.cpp b/src/o0settingsstore.cpp +index c475389..accba71 100644 +--- a/src/o0settingsstore.cpp ++++ b/src/o0settingsstore.cpp +@@ -1,28 +1,33 @@ + #include + #include + +-#include "o0settingsstore.h" ++#include "o2/o0settingsstore.h" + +-static quint64 getHash(const QString &encryptionKey) { ++static quint64 getHash(const QString &encryptionKey) ++{ + return QCryptographicHash::hash(encryptionKey.toLatin1(), QCryptographicHash::Sha1).toULongLong(); + } + + O0SettingsStore::O0SettingsStore(const QString &encryptionKey, QObject *parent): +- O0AbstractStore(parent), crypt_(getHash(encryptionKey)) { ++ O0AbstractStore(parent), crypt_(getHash(encryptionKey)) ++{ + settings_ = new QSettings(this); + } + + O0SettingsStore::O0SettingsStore(QSettings *settings, const QString &encryptionKey, QObject *parent): +- O0AbstractStore(parent), crypt_(getHash(encryptionKey)) { ++ O0AbstractStore(parent), crypt_(getHash(encryptionKey)) ++{ + settings_ = settings; + settings_->setParent(this); + } + +-QString O0SettingsStore::groupKey() const { ++QString O0SettingsStore::groupKey() const ++{ + return groupKey_; + } + +-void O0SettingsStore::setGroupKey(const QString &groupKey) { ++void O0SettingsStore::setGroupKey(const QString &groupKey) ++{ + if (groupKey_ == groupKey) { + return; + } +@@ -30,7 +35,8 @@ void O0SettingsStore::setGroupKey(const QString &groupKey) { + Q_EMIT groupKeyChanged(); + } + +-QString O0SettingsStore::value(const QString &key, const QString &defaultValue) { ++QString O0SettingsStore::value(const QString &key, const QString &defaultValue) ++{ + QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); + if (!settings_->contains(fullKey)) { + return defaultValue; +@@ -38,7 +44,8 @@ QString O0SettingsStore::value(const QString &key, const QString &defaultValue) + return crypt_.decryptToString(settings_->value(fullKey).toString()); + } + +-void O0SettingsStore::setValue(const QString &key, const QString &value) { ++void O0SettingsStore::setValue(const QString &key, const QString &value) ++{ + QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); + settings_->setValue(fullKey, crypt_.encryptToString(value)); + } +diff --git a/src/o0settingsstore.h b/src/o0settingsstore.h +index 2b6964a..4406503 100644 +--- a/src/o0settingsstore.h ++++ b/src/o0settingsstore.h +@@ -4,11 +4,12 @@ + #include + #include + +-#include "o0abstractstore.h" +-#include "o0simplecrypt.h" ++#include "o2/o0abstractstore.h" ++#include "o2/o0simplecrypt.h" + + /// Persistent storage for authentication tokens, using QSettings. +-class O0SettingsStore: public O0AbstractStore { ++class O0SettingsStore: public O0AbstractStore ++{ + Q_OBJECT + + public: +@@ -34,7 +35,7 @@ Q_SIGNALS: + void groupKeyChanged(); + + protected: +- QSettings* settings_; ++ QSettings *settings_; + QString groupKey_; + O0SimpleCrypt crypt_; + }; +diff --git a/src/o0simplecrypt.h b/src/o0simplecrypt.h +index 64f916d..b28c210 100644 +--- a/src/o0simplecrypt.h ++++ b/src/o0simplecrypt.h +@@ -109,7 +109,10 @@ public: + /** + Returns true if SimpleCrypt has been initialized with a key. + */ +- bool hasKey() const {return !m_keyParts.isEmpty();} ++ bool hasKey() const ++ { ++ return !m_keyParts.isEmpty(); ++ } + + /** + Sets the compression mode to use when encrypting data. The default mode is Auto. +@@ -117,11 +120,17 @@ public: + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ +- void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;} ++ void setCompressionMode(CompressionMode mode) ++ { ++ m_compressionMode = mode; ++ } + /** + Returns the CompressionMode that is currently in use. + */ +- CompressionMode compressionMode() const {return m_compressionMode;} ++ CompressionMode compressionMode() const ++ { ++ return m_compressionMode; ++ } + + /** + Sets the integrity mode to use when encrypting data. The default mode is Checksum. +@@ -129,23 +138,32 @@ public: + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ +- void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;} ++ void setIntegrityProtectionMode(IntegrityProtectionMode mode) ++ { ++ m_protectionMode = mode; ++ } + /** + Returns the IntegrityProtectionMode that is currently in use. + */ +- IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;} ++ IntegrityProtectionMode integrityProtectionMode() const ++ { ++ return m_protectionMode; ++ } + + /** + Returns the last error that occurred. + */ +- Error lastError() const {return m_lastError;} ++ Error lastError() const ++ { ++ return m_lastError; ++ } + + /** + Encrypts the @arg plaintext string with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the string, so it can be stored easily in a text format. + */ +- QString encryptToString(const QString& plaintext) ; ++ QString encryptToString(const QString &plaintext) ; + /** + Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the +@@ -159,7 +177,7 @@ public: + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ +- QByteArray encryptToByteArray(const QString& plaintext) ; ++ QByteArray encryptToByteArray(const QString &plaintext) ; + /** + Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. +@@ -176,7 +194,7 @@ public: + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ +- QString decryptToString(const QString& cyphertext) ; ++ QString decryptToString(const QString &cyphertext) ; + /** + Decrypts a cyphertext string encrypted with this class with the set key back to the + plain text version. +@@ -184,7 +202,7 @@ public: + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ +- QByteArray decryptToByteArray(const QString& cyphertext) ; ++ QByteArray decryptToByteArray(const QString &cyphertext) ; + /** + Decrypts a cyphertext binary encrypted with this class with the set key back to the + plain text version. +@@ -204,11 +222,11 @@ public: + + //enum to describe options that have been used for the encryption. Currently only one, but + //that only leaves room for future extensions like adding a cryptographic hash... +- enum CryptoFlag{CryptoFlagNone = 0, +- CryptoFlagCompression = 0x01, +- CryptoFlagChecksum = 0x02, +- CryptoFlagHash = 0x04 +- }; ++ enum CryptoFlag {CryptoFlagNone = 0, ++ CryptoFlagCompression = 0x01, ++ CryptoFlagChecksum = 0x02, ++ CryptoFlagHash = 0x04 ++ }; + Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag) + private: + +diff --git a/src/o1.cpp b/src/o1.cpp +index 47783fc..67660fc 100644 +--- a/src/o1.cpp ++++ b/src/o1.cpp +@@ -15,12 +15,13 @@ + #include + #endif + +-#include "o1.h" +-#include "o2replyserver.h" +-#include "o0globals.h" +-#include "o0settingsstore.h" ++#include "o2/o1.h" ++#include "o2/o2replyserver.h" ++#include "o2/o0globals.h" ++#include "o2/o0settingsstore.h" + +-O1::O1(QObject *parent): O0BaseAuth(parent) { ++O1::O1(QObject *parent): O0BaseAuth(parent) ++{ + setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); + manager_ = new QNetworkAccessManager(this); + replyServer_ = new O2ReplyServer(this); +@@ -29,59 +30,72 @@ O1::O1(QObject *parent): O0BaseAuth(parent) { + setCallbackUrl(O2_CALLBACK_URL); + } + +-QUrl O1::requestTokenUrl() { ++QUrl O1::requestTokenUrl() ++{ + return requestTokenUrl_; + } + +-void O1::setRequestTokenUrl(const QUrl &v) { ++void O1::setRequestTokenUrl(const QUrl &v) ++{ + requestTokenUrl_ = v; + Q_EMIT requestTokenUrlChanged(); + } + +-QList O1::requestParameters() { ++QList O1::requestParameters() ++{ + return requestParameters_; + } + +-void O1::setRequestParameters(const QList &v) { ++void O1::setRequestParameters(const QList &v) ++{ + requestParameters_ = v; + } + +-QString O1::callbackUrl() { ++QString O1::callbackUrl() ++{ + return callbackUrl_; + } + +-void O1::setCallbackUrl(const QString &v) { ++void O1::setCallbackUrl(const QString &v) ++{ + callbackUrl_ = v; + } + +-QUrl O1::authorizeUrl() { ++QUrl O1::authorizeUrl() ++{ + return authorizeUrl_; + } + +-void O1::setAuthorizeUrl(const QUrl &value) { ++void O1::setAuthorizeUrl(const QUrl &value) ++{ + authorizeUrl_ = value; + Q_EMIT authorizeUrlChanged(); + } + +-QUrl O1::accessTokenUrl() { ++QUrl O1::accessTokenUrl() ++{ + return accessTokenUrl_; + } + +-void O1::setAccessTokenUrl(const QUrl &value) { ++void O1::setAccessTokenUrl(const QUrl &value) ++{ + accessTokenUrl_ = value; + Q_EMIT accessTokenUrlChanged(); + } + +-QString O1::signatureMethod() { ++QString O1::signatureMethod() ++{ + return signatureMethod_; + } + +-void O1::setSignatureMethod(const QString &value) { ++void O1::setSignatureMethod(const QString &value) ++{ + qDebug() << "O1::setSignatureMethod: " << value; + signatureMethod_ = value; + } + +-void O1::unlink() { ++void O1::unlink() ++{ + qDebug() << "O1::unlink"; + setLinked(false); + setToken(""); +@@ -94,7 +108,8 @@ void O1::unlink() { + /// Calculate the HMAC variant of SHA1 hash. + /// @author http://qt-project.org/wiki/HMAC-SHA1. + /// @copyright Creative Commons Attribution-ShareAlike 2.5 Generic. +-static QByteArray hmacSha1(QByteArray key, QByteArray baseString) { ++static QByteArray hmacSha1(QByteArray key, QByteArray baseString) ++{ + int blockSize = 64; + if (key.length() > blockSize) { + key = QCryptographicHash::hash(key, QCryptographicHash::Sha1); +@@ -115,7 +130,8 @@ static QByteArray hmacSha1(QByteArray key, QByteArray baseString) { + #endif + + /// Get HTTP operation name. +-static QString getOperationName(QNetworkAccessManager::Operation op) { ++static QString getOperationName(QNetworkAccessManager::Operation op) ++{ + switch (op) { + case QNetworkAccessManager::GetOperation: return "GET"; + case QNetworkAccessManager::PostOperation: return "POST"; +@@ -126,12 +142,14 @@ static QString getOperationName(QNetworkAccessManager::Operation op) { + } + + /// Build a concatenated/percent-encoded string from a list of headers. +-QByteArray O1::encodeHeaders(const QList &headers) { ++QByteArray O1::encodeHeaders(const QList &headers) ++{ + return QUrl::toPercentEncoding(createQueryParameters(headers)); + } + + /// Build a base string for signing. +-QByteArray O1::getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) { ++QByteArray O1::getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) ++{ + QByteArray base; + + // Initialize base string with the operation name (e.g. "GET") and the base URL +@@ -147,7 +165,8 @@ QByteArray O1::getRequestBase(const QList &oauthParams, cons + return base; + } + +-QByteArray O1::sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) { ++QByteArray O1::sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) ++{ + QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op); + QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret); + #if QT_VERSION >= 0x050100 +@@ -157,7 +176,8 @@ QByteArray O1::sign(const QList &oauthParams, const QList &oauthParams) { ++QByteArray O1::buildAuthorizationHeader(const QList &oauthParams) ++{ + bool first = true; + QByteArray ret("OAuth "); + QList headers(oauthParams); +@@ -176,7 +196,8 @@ QByteArray O1::buildAuthorizationHeader(const QList &oauthPa + return ret; + } + +-QByteArray O1::generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) { ++QByteArray O1::generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) ++{ + QByteArray signature; + if (signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA1) { + signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret()); +@@ -186,7 +207,8 @@ QByteArray O1::generateSignature(const QList headers, const + return signature; + } + +-void O1::link() { ++void O1::link() ++{ + qDebug() << "O1::link"; + if (linked()) { + qDebug() << "O1::link: Linked already"; +@@ -205,8 +227,9 @@ void O1::link() { + // Get any query parameters for the request + QUrlQuery requestData; + O0RequestParameter param("", ""); +- foreach(param, requestParameters()) +- requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value))); ++ foreach (param, requestParameters()) { ++ requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value))); ++ } + + // Get the request url and add parameters + QUrl requestUrl = requestTokenUrl(); +@@ -237,13 +260,15 @@ void O1::link() { + connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished())); + } + +-void O1::onTokenRequestError(QNetworkReply::NetworkError error) { ++void O1::onTokenRequestError(QNetworkReply::NetworkError error) ++{ + QNetworkReply *reply = qobject_cast(sender()); + qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll(); + Q_EMIT linkingFailed(); + } + +-void O1::onTokenRequestFinished() { ++void O1::onTokenRequestFinished() ++{ + qDebug() << "O1::onTokenRequestFinished"; + QNetworkReply *reply = qobject_cast(sender()); + reply->deleteLater(); +@@ -282,7 +307,8 @@ void O1::onTokenRequestFinished() { + Q_EMIT openBrowser(url); + } + +-void O1::onVerificationReceived(QMap params) { ++void O1::onVerificationReceived(QMap params) ++{ + qDebug() << "O1::onVerificationReceived"; + Q_EMIT closeBrowser(); + verifier_ = params.value(O2_OAUTH_VERFIER, ""); +@@ -295,7 +321,8 @@ void O1::onVerificationReceived(QMap params) { + } + } + +-void O1::exchangeToken() { ++void O1::exchangeToken() ++{ + qDebug() << "O1::exchangeToken"; + + // Create token exchange request +@@ -318,13 +345,15 @@ void O1::exchangeToken() { + connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); + } + +-void O1::onTokenExchangeError(QNetworkReply::NetworkError error) { ++void O1::onTokenExchangeError(QNetworkReply::NetworkError error) ++{ + QNetworkReply *reply = qobject_cast(sender()); + qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll(); + Q_EMIT linkingFailed(); + } + +-void O1::onTokenExchangeFinished() { ++void O1::onTokenExchangeFinished() ++{ + qDebug() << "O1::onTokenExchangeFinished"; + + QNetworkReply *reply = qobject_cast(sender()); +@@ -344,7 +373,7 @@ void O1::onTokenExchangeFinished() { + if (!response.isEmpty()) { + QVariantMap extraTokens; + foreach (QString key, response.keys()) { +- extraTokens.insert(key, response.value(key)); ++ extraTokens.insert(key, response.value(key)); + } + setExtraTokens(extraTokens); + } +@@ -356,7 +385,8 @@ void O1::onTokenExchangeFinished() { + } + } + +-QMap O1::parseResponse(const QByteArray &response) { ++QMap O1::parseResponse(const QByteArray &response) ++{ + QMap ret; + foreach (QByteArray param, response.split('&')) { + QList kv = param.split('='); +@@ -367,7 +397,8 @@ QMap O1::parseResponse(const QByteArray &response) { + return ret; + } + +-QByteArray O1::nonce() { ++QByteArray O1::nonce() ++{ + static bool firstTime = true; + if (firstTime) { + firstTime = false; +diff --git a/src/o1.h b/src/o1.h +index 6d07b01..2330ca7 100644 +--- a/src/o1.h ++++ b/src/o1.h +@@ -5,12 +5,13 @@ + #include + #include + +-#include "o0baseauth.h" ++#include "o2/o0baseauth.h" + + class O2ReplyServer; + + /// Simple OAuth 1.0 authenticator. +-class O1: public O0BaseAuth { ++class O1: public O0BaseAuth ++{ + Q_OBJECT + + public: +@@ -92,7 +93,7 @@ Q_SIGNALS: + + protected Q_SLOTS: + /// Handle verification received from the reply server. +- virtual void onVerificationReceived(QMap params); ++ virtual void onVerificationReceived(QMap params); + + /// Handle token request error. + virtual void onTokenRequestError(QNetworkReply::NetworkError error); +diff --git a/src/o1requestor.cpp b/src/o1requestor.cpp +index fabb4fa..f3e049a 100644 +--- a/src/o1requestor.cpp ++++ b/src/o1requestor.cpp +@@ -3,41 +3,48 @@ + #include + #include + +-#include "o1requestor.h" +-#include "o1timedreply.h" +-#include "o0globals.h" ++#include "o2/o1requestor.h" ++#include "o2/o1timedreply.h" ++#include "o2/o0globals.h" + +-O1Requestor::O1Requestor(QNetworkAccessManager *manager, O1 *authenticator, QObject *parent): QObject(parent) { ++O1Requestor::O1Requestor(QNetworkAccessManager *manager, O1 *authenticator, QObject *parent): QObject(parent) ++{ + manager_ = manager; + authenticator_ = authenticator; + } + +-QNetworkReply *O1Requestor::get(const QNetworkRequest &req, const QList &signingParameters) { ++QNetworkReply *O1Requestor::get(const QNetworkRequest &req, const QList &signingParameters) ++{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::GetOperation); + return addTimer(manager_->get(request)); + } + +-QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) { ++QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) ++{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PostOperation); + return addTimer(manager_->post(request, data)); + } + +-QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, QHttpMultiPart * multiPart) { ++QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, QHttpMultiPart *multiPart) ++{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PostOperation); + return addTimer(manager_->post(request, multiPart)); + } + +-QNetworkReply *O1Requestor::put(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) { ++QNetworkReply *O1Requestor::put(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) ++{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PutOperation); + return addTimer(manager_->put(request, data)); + } + +-QNetworkReply *O1Requestor::addTimer(QNetworkReply *reply) { ++QNetworkReply *O1Requestor::addTimer(QNetworkReply *reply) ++{ + (void)new O1TimedReply(reply); + return reply; + } + +-QNetworkRequest O1Requestor::setup(const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) { ++QNetworkRequest O1Requestor::setup(const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) ++{ + // Collect OAuth parameters + QList oauthParams; + oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, authenticator_->clientId().toLatin1())); +diff --git a/src/o1requestor.h b/src/o1requestor.h +index 5de2fb2..0be21bc 100644 +--- a/src/o1requestor.h ++++ b/src/o1requestor.h +@@ -5,14 +5,15 @@ + #include + #include + +-#include "o1.h" ++#include "o2/o1.h" + + class QNetworkAccessManager; + class QNetworkReply; + class O1; + + /// Makes authenticated requests using OAuth 1.0. +-class O1Requestor: public QObject { ++class O1Requestor: public QObject ++{ + Q_OBJECT + + public: +@@ -57,5 +58,4 @@ protected: + O1 *authenticator_; + }; + +- + #endif // O1REQUESTOR_H +diff --git a/src/o1timedreply.cpp b/src/o1timedreply.cpp +index 14897f5..4685be2 100644 +--- a/src/o1timedreply.cpp ++++ b/src/o1timedreply.cpp +@@ -1,20 +1,23 @@ + #include + #include + +-#include "o1timedreply.h" ++#include "o2/o1timedreply.h" + +-O1TimedReply::O1TimedReply(QNetworkReply *parent, int pTimeout): QTimer(parent) { ++O1TimedReply::O1TimedReply(QNetworkReply *parent, int pTimeout): QTimer(parent) ++{ + setSingleShot(true); + setInterval(pTimeout); + connect(this, SIGNAL(timeout()), this, SLOT(onTimeout())); + connect(parent, SIGNAL(finished()), this, SLOT(onFinished())); + } + +-void O1TimedReply::onFinished() { ++void O1TimedReply::onFinished() ++{ + stop(); + Q_EMIT finished(); + } + +-void O1TimedReply::onTimeout() { ++void O1TimedReply::onTimeout() ++{ + Q_EMIT error(QNetworkReply::TimeoutError); + } +diff --git a/src/o1timedreply.h b/src/o1timedreply.h +index 174cc0c..ddb4565 100644 +--- a/src/o1timedreply.h ++++ b/src/o1timedreply.h +@@ -5,11 +5,12 @@ + #include + + /// A timer connected to a network reply. +-class O1TimedReply: public QTimer { ++class O1TimedReply: public QTimer ++{ + Q_OBJECT + + public: +- explicit O1TimedReply(QNetworkReply *parent, int pTimeout=60*1000); ++ explicit O1TimedReply(QNetworkReply *parent, int pTimeout = 60 * 1000); + + Q_SIGNALS: + /// Emitted when we have timed out waiting for the network reply. +diff --git a/src/o2.cpp b/src/o2.cpp +index e8108da..ec1799d 100644 +--- a/src/o2.cpp ++++ b/src/o2.cpp +@@ -26,7 +26,8 @@ + #include "o0settingsstore.h" + + /// Parse JSON data into a QVariantMap +-static QVariantMap parseTokenResponse(const QByteArray &data) { ++static QVariantMap parseTokenResponse(const QByteArray &data) ++{ + #if QT_VERSION >= 0x050000 + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(data, &err); +@@ -57,7 +58,8 @@ static QVariantMap parseTokenResponse(const QByteArray &data) { + } + + /// Add query parameters to a query +-static void addQueryParametersToUrl(QUrl &url, QList > parameters) { ++static void addQueryParametersToUrl(QUrl &url, QList > parameters) ++{ + #if QT_VERSION < 0x050000 + url.setQueryItems(parameters); + #else +@@ -67,7 +69,8 @@ static void addQueryParametersToUrl(QUrl &url, QList > + #endif + } + +-O2::O2(QObject *parent): O0BaseAuth(parent) { ++O2::O2(QObject *parent): O0BaseAuth(parent) ++{ + manager_ = new QNetworkAccessManager(this); + replyServer_ = new O2ReplyServer(this); + grantFlow_ = GrantFlowAuthorizationCode; +@@ -76,70 +79,85 @@ O2::O2(QObject *parent): O0BaseAuth(parent) { + connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + } + +-O2::GrantFlow O2::grantFlow() { ++O2::GrantFlow O2::grantFlow() ++{ + return grantFlow_; + } + +-void O2::setGrantFlow(O2::GrantFlow value) { ++void O2::setGrantFlow(O2::GrantFlow value) ++{ + grantFlow_ = value; + Q_EMIT grantFlowChanged(); + } + +-QString O2::username() { ++QString O2::username() ++{ + return username_; + } + +-void O2::setUsername(const QString &value) { ++void O2::setUsername(const QString &value) ++{ + username_ = value; + Q_EMIT usernameChanged(); + } + +-QString O2::password() { ++QString O2::password() ++{ + return password_; + } + +-void O2::setPassword(const QString &value) { ++void O2::setPassword(const QString &value) ++{ + password_ = value; + Q_EMIT passwordChanged(); + } + +-QString O2::scope() { ++QString O2::scope() ++{ + return scope_; + } + +-void O2::setScope(const QString &value) { ++void O2::setScope(const QString &value) ++{ + scope_ = value; + Q_EMIT scopeChanged(); + } + +-QString O2::requestUrl() { ++QString O2::requestUrl() ++{ + return requestUrl_.toString(); + } + +-void O2::setRequestUrl(const QString &value) { ++void O2::setRequestUrl(const QString &value) ++{ + requestUrl_ = value; + Q_EMIT requestUrlChanged(); + } + +-QString O2::tokenUrl() { ++QString O2::tokenUrl() ++{ + return tokenUrl_.toString(); + } + +-void O2::setTokenUrl(const QString &value) { +- tokenUrl_= value; ++void O2::setTokenUrl(const QString &value) ++{ ++ tokenUrl_ = value; + Q_EMIT tokenUrlChanged(); + } + +-QString O2::refreshTokenUrl() { ++QString O2::refreshTokenUrl() ++{ + return refreshTokenUrl_.toString(); + } + +-void O2::setRefreshTokenUrl(const QString &value) { ++void O2::setRefreshTokenUrl(const QString &value) ++{ + refreshTokenUrl_ = value; + Q_EMIT refreshTokenUrlChanged(); + } + +-void O2::link() { ++void O2::link() ++{ + qDebug() << "O2::link"; + + if (linked()) { +@@ -164,7 +182,7 @@ void O2::link() { + + // Assemble intial authentication URL + QList > parameters; +- parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE), (grantFlow_ == GrantFlowAuthorizationCode)? QString(O2_OAUTH2_GRANT_TYPE_CODE): QString(O2_OAUTH2_GRANT_TYPE_TOKEN))); ++ parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE), (grantFlow_ == GrantFlowAuthorizationCode) ? QString(O2_OAUTH2_GRANT_TYPE_CODE) : QString(O2_OAUTH2_GRANT_TYPE_TOKEN))); + parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_)); + parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_)); + parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_)); +@@ -196,7 +214,8 @@ void O2::link() { + } + } + +-void O2::unlink() { ++void O2::unlink() ++{ + qDebug() << "O2::unlink"; + setLinked(false); + setToken(QString()); +@@ -206,7 +225,8 @@ void O2::unlink() { + Q_EMIT linkingSucceeded(); + } + +-void O2::onVerificationReceived(const QMap response) { ++void O2::onVerificationReceived(const QMap response) ++{ + qDebug() << "O2::onVerificationReceived:" << response; + qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()"; + Q_EMIT closeBrowser(); +@@ -223,8 +243,9 @@ void O2::onVerificationReceived(const QMap response) { + + // Exchange access code for access/refresh tokens + QString query; +- if(!apiKey_.isEmpty()) ++ if (!apiKey_.isEmpty()) { + query = QString("?" + QString(O2_OAUTH2_API_KEY) + "=" + apiKey_); ++ } + QNetworkRequest tokenRequest(QUrl(tokenUrl_.toString() + query)); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QMap parameters; +@@ -244,17 +265,20 @@ void O2::onVerificationReceived(const QMap response) { + } + } + +-QString O2::code() { ++QString O2::code() ++{ + QString key = QString(O2_KEY_CODE).arg(clientId_); + return store_->value(key); + } + +-void O2::setCode(const QString &c) { ++void O2::setCode(const QString &c) ++{ + QString key = QString(O2_KEY_CODE).arg(clientId_); + store_->setValue(key, c); + } + +-void O2::onTokenReplyFinished() { ++void O2::onTokenReplyFinished() ++{ + qDebug() << "O2::onTokenReplyFinished"; + QNetworkReply *tokenReply = qobject_cast(sender()); + if (tokenReply->error() == QNetworkReply::NoError) { +@@ -283,7 +307,8 @@ void O2::onTokenReplyFinished() { + tokenReply->deleteLater(); + } + +-void O2::onTokenReplyError(QNetworkReply::NetworkError error) { ++void O2::onTokenReplyError(QNetworkReply::NetworkError error) ++{ + QNetworkReply *tokenReply = qobject_cast(sender()); + qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString(); + qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll(); +@@ -293,7 +318,8 @@ void O2::onTokenReplyError(QNetworkReply::NetworkError error) { + Q_EMIT linkingFailed(); + } + +-QByteArray O2::buildRequestBody(const QMap ¶meters) { ++QByteArray O2::buildRequestBody(const QMap ¶meters) ++{ + QByteArray body; + bool first = true; + foreach (QString key, parameters.keys()) { +@@ -308,28 +334,33 @@ QByteArray O2::buildRequestBody(const QMap ¶meters) { + return body; + } + +-int O2::expires() { ++int O2::expires() ++{ + QString key = QString(O2_KEY_EXPIRES).arg(clientId_); + return store_->value(key).toInt(); + } + +-void O2::setExpires(int v) { ++void O2::setExpires(int v) ++{ + QString key = QString(O2_KEY_EXPIRES).arg(clientId_); + store_->setValue(key, QString::number(v)); + } + +-QString O2::refreshToken() { ++QString O2::refreshToken() ++{ + QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_); + return store_->value(key); + } + +-void O2::setRefreshToken(const QString &v) { ++void O2::setRefreshToken(const QString &v) ++{ + qDebug() << "O2::setRefreshToken" << v.left(4) << "..."; + QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_); + store_->setValue(key, v); + } + +-void O2::refresh() { ++void O2::refresh() ++{ + qDebug() << "O2::refresh: Token: ..." << refreshToken().right(7); + + if (refreshToken().isEmpty()) { +@@ -358,7 +389,8 @@ void O2::refresh() { + connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + } + +-void O2::onRefreshFinished() { ++void O2::onRefreshFinished() ++{ + QNetworkReply *refreshReply = qobject_cast(sender()); + qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); + if (refreshReply->error() == QNetworkReply::NoError) { +@@ -376,7 +408,8 @@ void O2::onRefreshFinished() { + refreshReply->deleteLater(); + } + +-void O2::onRefreshError(QNetworkReply::NetworkError error) { ++void O2::onRefreshError(QNetworkReply::NetworkError error) ++{ + QNetworkReply *refreshReply = qobject_cast(sender()); + qWarning() << "O2::onRefreshError: " << error; + unlink(); +@@ -384,34 +417,42 @@ void O2::onRefreshError(QNetworkReply::NetworkError error) { + Q_EMIT refreshFinished(error); + } + +-QString O2::localhostPolicy() const { ++QString O2::localhostPolicy() const ++{ + return localhostPolicy_; + } + +-void O2::setLocalhostPolicy(const QString &value) { ++void O2::setLocalhostPolicy(const QString &value) ++{ + localhostPolicy_ = value; + } + +-QString O2::apiKey() { ++QString O2::apiKey() ++{ + return apiKey_; + } + +-void O2::setApiKey(const QString &value) { ++void O2::setApiKey(const QString &value) ++{ + apiKey_ = value; + } + +-QByteArray O2::replyContent() { ++QByteArray O2::replyContent() ++{ + return replyServer_->replyContent(); + } + +-void O2::setReplyContent(const QByteArray &value) { ++void O2::setReplyContent(const QByteArray &value) ++{ + replyServer_->setReplyContent(value); + } + +-bool O2::ignoreSslErrors() { ++bool O2::ignoreSslErrors() ++{ + return timedReplies_.ignoreSslErrors(); + } + +-void O2::setIgnoreSslErrors(bool ignoreSslErrors) { ++void O2::setIgnoreSslErrors(bool ignoreSslErrors) ++{ + timedReplies_.setIgnoreSslErrors(ignoreSslErrors); + } +diff --git a/src/o2.h b/src/o2.h +index 250ea50..3f7591b 100644 +--- a/src/o2.h ++++ b/src/o2.h +@@ -13,7 +13,8 @@ + class O2ReplyServer; + + /// Simple OAuth2 authenticator. +-class O2: public O0BaseAuth { ++class O2: public O0BaseAuth ++{ + Q_OBJECT + Q_ENUMS(GrantFlow) + +diff --git a/src/o2reply.cpp b/src/o2reply.cpp +index 4e03172..db8517a 100644 +--- a/src/o2reply.cpp ++++ b/src/o2reply.cpp +@@ -1,36 +1,43 @@ + #include + #include + +-#include "o2reply.h" ++#include "o2/o2reply.h" + +-O2Reply::O2Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) { ++O2Reply::O2Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) ++{ + setSingleShot(true); + connect(this, SIGNAL(error(QNetworkReply::NetworkError)), reply, SIGNAL(error(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(this, SIGNAL(timeout()), this, SLOT(onTimeOut()), Qt::QueuedConnection); + start(timeOut); + } + +-void O2Reply::onTimeOut() { ++void O2Reply::onTimeOut() ++{ + Q_EMIT error(QNetworkReply::TimeoutError); + } + +-O2ReplyList::~O2ReplyList() { ++O2ReplyList::~O2ReplyList() ++{ + foreach (O2Reply *timedReply, replies_) { + delete timedReply; + } + } + +-void O2ReplyList::add(QNetworkReply *reply) { +- if (reply && ignoreSslErrors()) +- reply->ignoreSslErrors(); ++void O2ReplyList::add(QNetworkReply *reply) ++{ ++ if (reply && ignoreSslErrors()) { ++ reply->ignoreSslErrors(); ++ } + add(new O2Reply(reply)); + } + +-void O2ReplyList::add(O2Reply *reply) { ++void O2ReplyList::add(O2Reply *reply) ++{ + replies_.append(reply); + } + +-void O2ReplyList::remove(QNetworkReply *reply) { ++void O2ReplyList::remove(QNetworkReply *reply) ++{ + O2Reply *o2Reply = find(reply); + if (o2Reply) { + o2Reply->stop(); +@@ -38,7 +45,8 @@ void O2ReplyList::remove(QNetworkReply *reply) { + } + } + +-O2Reply *O2ReplyList::find(QNetworkReply *reply) { ++O2Reply *O2ReplyList::find(QNetworkReply *reply) ++{ + foreach (O2Reply *timedReply, replies_) { + if (timedReply->reply == reply) { + return timedReply; +diff --git a/src/o2reply.h b/src/o2reply.h +index 15f1571..4bfcd51 100644 +--- a/src/o2reply.h ++++ b/src/o2reply.h +@@ -9,7 +9,8 @@ + #include + + /// A network request/reply pair that can time out. +-class O2Reply: public QTimer { ++class O2Reply: public QTimer ++{ + Q_OBJECT + + public: +@@ -27,9 +28,13 @@ public: + }; + + /// List of O2Replies. +-class O2ReplyList { ++class O2ReplyList ++{ + public: +- O2ReplyList() { ignoreSslErrors_ = false; } ++ O2ReplyList() ++ { ++ ignoreSslErrors_ = false; ++ } + + /// Destructor. + /// Deletes all O2Reply instances in the list. +diff --git a/src/o2replyserver.cpp b/src/o2replyserver.cpp +index 00dad96..b4c5da7 100644 +--- a/src/o2replyserver.cpp ++++ b/src/o2replyserver.cpp +@@ -11,20 +11,23 @@ + #include + #endif + +-#include "o2replyserver.h" ++#include "o2/o2replyserver.h" + +-O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent) { ++O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent) ++{ + connect(this, SIGNAL(newConnection()), this, SLOT(onIncomingConnection())); + replyContent_ = ""; + } + +-void O2ReplyServer::onIncomingConnection() { ++void O2ReplyServer::onIncomingConnection() ++{ + QTcpSocket *socket = nextPendingConnection(); + connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection); + connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); + } + +-void O2ReplyServer::onBytesReady() { ++void O2ReplyServer::onBytesReady() ++{ + qDebug() << "O2ReplyServer::onBytesReady"; + QTcpSocket *socket = qobject_cast(sender()); + if (!socket) { +@@ -44,7 +47,8 @@ void O2ReplyServer::onBytesReady() { + Q_EMIT verificationReceived(queryParams); + } + +-QMap O2ReplyServer::parseQueryParams(QByteArray *data) { ++QMap O2ReplyServer::parseQueryParams(QByteArray *data) ++{ + qDebug() << "O2ReplyServer::parseQueryParams"; + + QString splitGetLine = QString(*data).split("\r\n").first(); +@@ -72,10 +76,12 @@ QMap O2ReplyServer::parseQueryParams(QByteArray *data) { + return queryParams; + } + +-QByteArray O2ReplyServer::replyContent() { ++QByteArray O2ReplyServer::replyContent() ++{ + return replyContent_; + } + +-void O2ReplyServer::setReplyContent(const QByteArray &value) { ++void O2ReplyServer::setReplyContent(const QByteArray &value) ++{ + replyContent_ = value; + } +diff --git a/src/o2replyserver.h b/src/o2replyserver.h +index 5a9d2f2..f464126 100644 +--- a/src/o2replyserver.h ++++ b/src/o2replyserver.h +@@ -7,7 +7,8 @@ + #include + + /// HTTP server to process authentication response. +-class O2ReplyServer: public QTcpServer { ++class O2ReplyServer: public QTcpServer ++{ + Q_OBJECT + + public: +diff --git a/src/o2requestor.cpp b/src/o2requestor.cpp +index d737d35..89248a5 100644 +--- a/src/o2requestor.cpp ++++ b/src/o2requestor.cpp +@@ -4,11 +4,12 @@ + #include + #endif + +-#include "o2requestor.h" +-#include "o2.h" +-#include "o0globals.h" ++#include "o2/o2requestor.h" ++#include "o2/o2.h" ++#include "o2/o0globals.h" + +-O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent): QObject(parent), reply_(NULL), status_(Idle) { ++O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent): QObject(parent), reply_(NULL), status_(Idle) ++{ + manager_ = manager; + authenticator_ = authenticator; + if (authenticator) { +@@ -18,10 +19,12 @@ O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObj + connect(authenticator, SIGNAL(refreshFinished(QNetworkReply::NetworkError)), this, SLOT(onRefreshFinished(QNetworkReply::NetworkError)), Qt::QueuedConnection); + } + +-O2Requestor::~O2Requestor() { ++O2Requestor::~O2Requestor() ++{ + } + +-int O2Requestor::get(const QNetworkRequest &req) { ++int O2Requestor::get(const QNetworkRequest &req) ++{ + if (-1 == setup(req, QNetworkAccessManager::GetOperation)) { + return -1; + } +@@ -32,7 +35,8 @@ int O2Requestor::get(const QNetworkRequest &req) { + return id_; + } + +-int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data) { ++int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data) ++{ + if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { + return -1; + } +@@ -45,7 +49,8 @@ int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data) { + return id_; + } + +-int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data) { ++int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data) ++{ + if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { + return -1; + } +@@ -58,7 +63,8 @@ int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data) { + return id_; + } + +-void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) { ++void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) ++{ + if (status_ != Requesting) { + qWarning() << "O2Requestor::onRefreshFinished: No pending request"; + return; +@@ -71,7 +77,8 @@ void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) { + } + } + +-void O2Requestor::onRequestFinished() { ++void O2Requestor::onRequestFinished() ++{ + QNetworkReply *senderReply = qobject_cast(sender()); + QNetworkReply::NetworkError error = senderReply->error(); + if (status_ == Idle) { +@@ -85,7 +92,8 @@ void O2Requestor::onRequestFinished() { + } + } + +-void O2Requestor::onRequestError(QNetworkReply::NetworkError error) { ++void O2Requestor::onRequestError(QNetworkReply::NetworkError error) ++{ + qWarning() << "O2Requestor::onRequestError: Error" << (int)error; + if (status_ == Idle) { + return; +@@ -107,7 +115,8 @@ void O2Requestor::onRequestError(QNetworkReply::NetworkError error) { + QTimer::singleShot(10, this, SLOT(finish())); + } + +-void O2Requestor::onUploadProgress(qint64 uploaded, qint64 total) { ++void O2Requestor::onUploadProgress(qint64 uploaded, qint64 total) ++{ + if (status_ == Idle) { + qWarning() << "O2Requestor::onUploadProgress: No pending request"; + return; +@@ -118,7 +127,8 @@ void O2Requestor::onUploadProgress(qint64 uploaded, qint64 total) { + Q_EMIT uploadProgress(id_, uploaded, total); + } + +-int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation) { ++int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation) ++{ + static int currentId; + QUrl url; + +@@ -144,7 +154,8 @@ int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operat + return id_; + } + +-void O2Requestor::finish() { ++void O2Requestor::finish() ++{ + QByteArray data; + if (status_ == Idle) { + qWarning() << "O2Requestor::finish: No pending request"; +@@ -158,7 +169,8 @@ void O2Requestor::finish() { + Q_EMIT finished(id_, error_, data); + } + +-void O2Requestor::retry() { ++void O2Requestor::retry() ++{ + if (status_ != Requesting) { + qWarning() << "O2Requestor::retry: No pending request"; + return; +diff --git a/src/o2requestor.h b/src/o2requestor.h +index 939e598..fef7fc0 100644 +--- a/src/o2requestor.h ++++ b/src/o2requestor.h +@@ -8,12 +8,13 @@ + #include + #include + +-#include "o2reply.h" ++#include "o2/o2reply.h" + + class O2; + + /// Makes authenticated requests. +-class O2Requestor: public QObject { ++class O2Requestor: public QObject ++{ + Q_OBJECT + + public: +diff --git a/src/o2simplecrypt.cpp b/src/o2simplecrypt.cpp +index 210ae12..4a8584c 100644 +--- a/src/o2simplecrypt.cpp ++++ b/src/o2simplecrypt.cpp +@@ -24,7 +24,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +-#include "o0simplecrypt.h" ++#include "o2/o0simplecrypt.h" + #include + #include + #include +@@ -61,16 +61,17 @@ void O0SimpleCrypt::splitKey() + { + m_keyParts.clear(); + m_keyParts.resize(8); +- for (int i=0;i<8;i++) { ++ for (int i = 0; i < 8; i++) { + quint64 part = m_key; +- for (int j=i; j>0; j--) ++ for (int j = i; j > 0; j--) { + part = part >> 8; ++ } + part = part & 0xff; + m_keyParts[i] = static_cast(part); + } + } + +-QByteArray O0SimpleCrypt::encryptToByteArray(const QString& plaintext) ++QByteArray O0SimpleCrypt::encryptToByteArray(const QString &plaintext) + { + QByteArray plaintextArray = plaintext.toUtf8(); + return encryptToByteArray(plaintextArray); +@@ -84,7 +85,6 @@ QByteArray O0SimpleCrypt::encryptToByteArray(QByteArray plaintext) + return QByteArray(); + } + +- + QByteArray ba = plaintext; + + CryptoFlags flags = CryptoFlagNone; +@@ -136,7 +136,7 @@ QByteArray O0SimpleCrypt::encryptToByteArray(QByteArray plaintext) + return resultArray; + } + +-QString O0SimpleCrypt::encryptToString(const QString& plaintext) ++QString O0SimpleCrypt::encryptToString(const QString &plaintext) + { + QByteArray plaintextArray = plaintext.toUtf8(); + QByteArray cypher = encryptToByteArray(plaintextArray); +@@ -168,7 +168,7 @@ QString O0SimpleCrypt::decryptToString(QByteArray cypher) + return plaintext; + } + +-QByteArray O0SimpleCrypt::decryptToByteArray(const QString& cyphertext) ++QByteArray O0SimpleCrypt::decryptToByteArray(const QString &cyphertext) + { + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray ba = decryptToByteArray(cyphertextArray); +@@ -193,7 +193,7 @@ QByteArray O0SimpleCrypt::decryptToByteArray(QByteArray cypher) + + char version = ba.at(0); + +- if (version !=3) { //we only work with version 3 ++ if (version != 3) { //we only work with version 3 + m_lastError = ErrorUnknownVersion; + qWarning() << "Invalid version or not a cyphertext."; + return QByteArray(); +@@ -246,8 +246,9 @@ QByteArray O0SimpleCrypt::decryptToByteArray(QByteArray cypher) + return QByteArray(); + } + +- if (flags.testFlag(CryptoFlagCompression)) ++ if (flags.testFlag(CryptoFlagCompression)) { + ba = qUncompress(ba); ++ } + + m_lastError = ErrorNoError; + return ba; diff --git a/resources/tomboynotes/o2/o0abstractstore.h b/resources/tomboynotes/o2/o0abstractstore.h new file mode 100644 index 000000000..04643257a --- /dev/null +++ b/resources/tomboynotes/o2/o0abstractstore.h @@ -0,0 +1,24 @@ +#ifndef O0ABSTRACTSTORE_H +#define O0ABSTRACTSTORE_H + +#include +#include + +/// Storage for strings. +class O0AbstractStore: public QObject +{ + Q_OBJECT + +public: + explicit O0AbstractStore(QObject *parent = 0): QObject(parent) + { + } + + /// Retrieve a string value by key. + virtual QString value(const QString &key, const QString &defaultValue = QString()) = 0; + + /// Set a string value for a key. + virtual void setValue(const QString &key, const QString &value) = 0; +}; + +#endif // O0ABSTRACTSTORE_H diff --git a/resources/tomboynotes/o2/o0baseauth.cpp b/resources/tomboynotes/o2/o0baseauth.cpp new file mode 100644 index 000000000..c021776f3 --- /dev/null +++ b/resources/tomboynotes/o2/o0baseauth.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include "o2/o0baseauth.h" +#include "o2/o0globals.h" +#include "o2/o0settingsstore.h" + +static const quint16 DefaultLocalPort = 1965; + +O0BaseAuth::O0BaseAuth(QObject *parent): QObject(parent) +{ + localPort_ = DefaultLocalPort; + store_ = new O0SettingsStore(O2_ENCRYPTION_KEY, this); +} + +void O0BaseAuth::setStore(O0AbstractStore *store) +{ + if (store_) { + store_->deleteLater(); + } + if (store) { + store_ = store; + store_->setParent(this); + } else { + store_ = new O0SettingsStore(O2_ENCRYPTION_KEY, this); + return; + } +} + +bool O0BaseAuth::linked() +{ + QString key = QString(O2_KEY_LINKED).arg(clientId_); + bool result = !store_->value(key).isEmpty(); + qDebug() << "O0BaseAuth::linked:" << (result ? "Yes" : "No"); + return result; +} + +void O0BaseAuth::setLinked(bool v) +{ + qDebug() << "O0BaseAuth::setLinked:" << (v ? "true" : "false"); + bool oldValue = linked(); + QString key = QString(O2_KEY_LINKED).arg(clientId_); + store_->setValue(key, v ? "1" : ""); + if (oldValue != v) { + Q_EMIT linkedChanged(); + } +} + +QString O0BaseAuth::tokenSecret() +{ + QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); + return store_->value(key); +} + +void O0BaseAuth::setTokenSecret(const QString &v) +{ + QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); + store_->setValue(key, v); + Q_EMIT tokenSecretChanged(); +} + +QString O0BaseAuth::token() +{ + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + return store_->value(key); +} + +void O0BaseAuth::setToken(const QString &v) +{ + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + store_->setValue(key, v); + Q_EMIT tokenChanged(); +} + +QString O0BaseAuth::clientId() +{ + return clientId_; +} + +void O0BaseAuth::setClientId(const QString &value) +{ + clientId_ = value; + Q_EMIT clientIdChanged(); +} + +QString O0BaseAuth::clientSecret() +{ + return clientSecret_; +} + +void O0BaseAuth::setClientSecret(const QString &value) +{ + clientSecret_ = value; + Q_EMIT clientSecretChanged(); +} + +int O0BaseAuth::localPort() +{ + return localPort_; +} + +void O0BaseAuth::setLocalPort(int value) +{ + qDebug() << "O0BaseAuth::setLocalPort:" << value; + localPort_ = value; + Q_EMIT localPortChanged(); +} + +QVariantMap O0BaseAuth::extraTokens() +{ + QString key = QString(O2_KEY_EXTRA_TOKENS).arg(clientId_); + QString value = store_->value(key); + QByteArray bytes = QByteArray::fromBase64(value.toLatin1()); + QDataStream stream(&bytes, QIODevice::ReadOnly); + stream >> extraTokens_; + return extraTokens_; +} + +void O0BaseAuth::setExtraTokens(QVariantMap extraTokens) +{ + extraTokens_ = extraTokens; + QByteArray bytes; + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << extraTokens; + QString key = QString(O2_KEY_EXTRA_TOKENS).arg(clientId_); + store_->setValue(key, bytes.toBase64()); + Q_EMIT extraTokensChanged(); +} + +QByteArray O0BaseAuth::createQueryParameters(const QList ¶meters) +{ + QByteArray ret; + bool first = true; + foreach (O0RequestParameter h, parameters) { + if (first) { + first = false; + } else { + ret.append("&"); + } + ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value)); + } + return ret; +} diff --git a/resources/tomboynotes/o2/o0baseauth.h b/resources/tomboynotes/o2/o0baseauth.h new file mode 100644 index 000000000..c906edf15 --- /dev/null +++ b/resources/tomboynotes/o2/o0baseauth.h @@ -0,0 +1,121 @@ +#ifndef O0BASEAUTH_H +#define O0BASEAUTH_H + +#include +#include +#include +#include +#include +#include + +#include "o2/o0abstractstore.h" +#include "o2/o0requestparameter.h" + +/// Base class of OAuth authenticators +class O0BaseAuth : public QObject +{ + Q_OBJECT + +public: + explicit O0BaseAuth(QObject *parent = 0); + +public: + /// Are we authenticated? + Q_PROPERTY(bool linked READ linked WRITE setLinked NOTIFY linkedChanged) + bool linked(); + + /// Authentication token. + Q_PROPERTY(QString token READ token NOTIFY tokenChanged) + QString token(); + + /// Authentication token secret. + Q_PROPERTY(QString tokenSecret READ tokenSecret NOTIFY tokenSecretChanged) + QString tokenSecret(); + + /// Provider-specific extra tokens, available after a successful authentication + Q_PROPERTY(QVariantMap extraTokens READ extraTokens NOTIFY extraTokensChanged) + QVariantMap extraTokens(); + + /// Client application ID. + /// O1 instances with the same (client ID, client secret) share the same "linked", "token" and "tokenSecret" properties. + Q_PROPERTY(QString clientId READ clientId WRITE setClientId NOTIFY clientIdChanged) + QString clientId(); + void setClientId(const QString &value); + + /// Client application secret. + /// O1 instances with the same (client ID, client secret) share the same "linked", "token" and "tokenSecret" properties. + Q_PROPERTY(QString clientSecret READ clientSecret WRITE setClientSecret NOTIFY clientSecretChanged) + QString clientSecret(); + void setClientSecret(const QString &value); + + /// TCP port number to use in local redirections. + /// The OAuth "redirect_uri" will be set to "http://localhost:/". + /// If localPort is set to 0 (default), O2 will replace it with a free one. + Q_PROPERTY(int localPort READ localPort WRITE setLocalPort NOTIFY localPortChanged) + int localPort(); + void setLocalPort(int value); + + /// Sets the storage object to use for storing the OAuth tokens on a peristent medium + void setStore(O0AbstractStore *store); + + /// Construct query string from list of headers + static QByteArray createQueryParameters(const QList ¶meters); + +public Q_SLOTS: + /// Authenticate. + Q_INVOKABLE virtual void link() = 0; + + /// De-authenticate. + Q_INVOKABLE virtual void unlink() = 0; + +Q_SIGNALS: + /// Emitted when client needs to open a web browser window, with the given URL. + void openBrowser(const QUrl &url); + + /// Emitted when client can close the browser window. + void closeBrowser(); + + /// Emitted when authentication/deauthentication succeeded. + void linkingSucceeded(); + + /// Emitted when authentication/deauthentication failed. + void linkingFailed(); + + // Property change signals + + void linkedChanged(); + void clientIdChanged(); + void clientSecretChanged(); + void localPortChanged(); + void tokenChanged(); + void tokenSecretChanged(); + void extraTokensChanged(); + +protected: + /// Set authentication token. + void setToken(const QString &v); + + /// Set authentication token secret. + void setTokenSecret(const QString &v); + + /// Set the linked state + void setLinked(bool v); + + /// Set extra tokens found in OAuth response + void setExtraTokens(QVariantMap extraTokens); + +protected: + QString clientId_; + QString clientSecret_; + QString redirectUri_; + QString requestToken_; + QString requestTokenSecret_; + QUrl requestTokenUrl_; + QUrl authorizeUrl_; + QUrl accessTokenUrl_; + quint16 localPort_; + O0AbstractStore *store_; + QVariantMap extraTokens_; +}; + +#endif // O0BASEAUTH diff --git a/resources/tomboynotes/o2/o0globals.h b/resources/tomboynotes/o2/o0globals.h new file mode 100644 index 000000000..4ce7147cb --- /dev/null +++ b/resources/tomboynotes/o2/o0globals.h @@ -0,0 +1,61 @@ +#ifndef O0GLOBALS_H +#define O0GLOBALS_H + +// Common constants +const char O2_ENCRYPTION_KEY[] = "12345678"; +const char O2_CALLBACK_URL[] = "http://127.0.0.1:%1/"; +const char O2_MIME_TYPE_XFORM[] = "application/x-www-form-urlencoded"; + +// QSettings key names +const char O2_KEY_TOKEN[] = "token.%1"; +const char O2_KEY_TOKEN_SECRET[] = "tokensecret.%1"; +const char O2_KEY_CODE[] = "code.%1"; +const char O2_KEY_EXPIRES[] = "expires.%1"; +const char O2_KEY_REFRESH_TOKEN[] = "refreshtoken.%1"; +const char O2_KEY_LINKED[] = "linked.%1"; +const char O2_KEY_EXTRA_TOKENS[] = "extratokens.%1"; + +// OAuth 1/1.1 Request Parameters +const char O2_OAUTH_CALLBACK[] = "oauth_callback"; +const char O2_OAUTH_CONSUMER_KEY[] = "oauth_consumer_key"; +const char O2_OAUTH_NONCE[] = "oauth_nonce"; +const char O2_OAUTH_SIGNATURE[] = "oauth_signature"; +const char O2_OAUTH_SIGNATURE_METHOD[] = "oauth_signature_method"; +const char O2_OAUTH_TIMESTAMP[] = "oauth_timestamp"; +const char O2_OAUTH_VERSION[] = "oauth_version"; +// OAuth 1/1.1 Response Parameters +const char O2_OAUTH_TOKEN[] = "oauth_token"; +const char O2_OAUTH_TOKEN_SECRET[] = "oauth_token_secret"; +const char O2_OAUTH_CALLBACK_CONFIRMED[] = "oauth_callback_confirmed"; +const char O2_OAUTH_VERFIER[] = "oauth_verifier"; + +// OAuth 2 Request Parameters +const char O2_OAUTH2_RESPONSE_TYPE[] = "response_type"; +const char O2_OAUTH2_CLIENT_ID[] = "client_id"; +const char O2_OAUTH2_CLIENT_SECRET[] = "client_secret"; +const char O2_OAUTH2_USERNAME[] = "username"; +const char O2_OAUTH2_PASSWORD[] = "password"; +const char O2_OAUTH2_REDIRECT_URI[] = "redirect_uri"; +const char O2_OAUTH2_SCOPE[] = "scope"; +const char O2_OAUTH2_GRANT_TYPE_CODE[] = "code"; +const char O2_OAUTH2_GRANT_TYPE_TOKEN[] = "token"; +const char O2_OAUTH2_GRANT_TYPE_PASSWORD[] = "password"; +const char O2_OAUTH2_GRANT_TYPE[] = "grant_type"; +const char O2_OAUTH2_API_KEY[] = "api_key"; + +// OAuth 2 Response Parameters +const char O2_OAUTH2_ACCESS_TOKEN[] = "access_token"; +const char O2_OAUTH2_REFRESH_TOKEN[] = "refresh_token"; +const char O2_OAUTH2_EXPIRES_IN[] = "expires_in"; + +// OAuth signature types +const char O2_SIGNATURE_TYPE_HMAC_SHA1[] = "HMAC-SHA1"; +const char O2_SIGNATURE_TYPE_PLAINTEXT[] = "PLAINTEXT"; + +// Parameter values +const char O2_AUTHORIZATION_CODE[] = "authorization_code"; + +// Standard HTTP headers +const char O2_HTTP_AUTHORIZATION_HEADER[] = "Authorization"; + +#endif // O0GLOBALS_H diff --git a/resources/tomboynotes/o2/o0requestparameter.h b/resources/tomboynotes/o2/o0requestparameter.h new file mode 100644 index 000000000..c96698734 --- /dev/null +++ b/resources/tomboynotes/o2/o0requestparameter.h @@ -0,0 +1,15 @@ +#ifndef O0REQUESTPARAMETER_H +#define O0REQUESTPARAMETER_H + +/// Request parameter (name-value pair) participating in authentication. +struct O0RequestParameter { + O0RequestParameter(const QByteArray &n, const QByteArray &v): name(n), value(v) {} + bool operator <(const O0RequestParameter &other) const + { + return (name == other.name) ? (value < other.value) : (name < other.name); + } + QByteArray name; + QByteArray value; +}; + +#endif // O0REQUESTPARAMETER_H diff --git a/resources/tomboynotes/o2/o0settingsstore.cpp b/resources/tomboynotes/o2/o0settingsstore.cpp new file mode 100644 index 000000000..accba718b --- /dev/null +++ b/resources/tomboynotes/o2/o0settingsstore.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include "o2/o0settingsstore.h" + +static quint64 getHash(const QString &encryptionKey) +{ + return QCryptographicHash::hash(encryptionKey.toLatin1(), QCryptographicHash::Sha1).toULongLong(); +} + +O0SettingsStore::O0SettingsStore(const QString &encryptionKey, QObject *parent): + O0AbstractStore(parent), crypt_(getHash(encryptionKey)) +{ + settings_ = new QSettings(this); +} + +O0SettingsStore::O0SettingsStore(QSettings *settings, const QString &encryptionKey, QObject *parent): + O0AbstractStore(parent), crypt_(getHash(encryptionKey)) +{ + settings_ = settings; + settings_->setParent(this); +} + +QString O0SettingsStore::groupKey() const +{ + return groupKey_; +} + +void O0SettingsStore::setGroupKey(const QString &groupKey) +{ + if (groupKey_ == groupKey) { + return; + } + groupKey_ = groupKey; + Q_EMIT groupKeyChanged(); +} + +QString O0SettingsStore::value(const QString &key, const QString &defaultValue) +{ + QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); + if (!settings_->contains(fullKey)) { + return defaultValue; + } + return crypt_.decryptToString(settings_->value(fullKey).toString()); +} + +void O0SettingsStore::setValue(const QString &key, const QString &value) +{ + QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); + settings_->setValue(fullKey, crypt_.encryptToString(value)); +} diff --git a/resources/tomboynotes/o2/o0settingsstore.h b/resources/tomboynotes/o2/o0settingsstore.h new file mode 100644 index 000000000..440650306 --- /dev/null +++ b/resources/tomboynotes/o2/o0settingsstore.h @@ -0,0 +1,43 @@ +#ifndef O0SETTINGSSTORE_H +#define O0SETTINGSSTORE_H + +#include +#include + +#include "o2/o0abstractstore.h" +#include "o2/o0simplecrypt.h" + +/// Persistent storage for authentication tokens, using QSettings. +class O0SettingsStore: public O0AbstractStore +{ + Q_OBJECT + +public: + /// Constructor + explicit O0SettingsStore(const QString &encryptionKey, QObject *parent = 0); + + /// Construct with an explicit QSettings instance + explicit O0SettingsStore(QSettings *settings, const QString &encryptionKey, QObject *parent = 0); + + /// Group key prefix + Q_PROPERTY(QString groupKey READ groupKey WRITE setGroupKey NOTIFY groupKeyChanged) + QString groupKey() const; + void setGroupKey(const QString &groupKey); + + /// Get a string value for a key + QString value(const QString &key, const QString &defaultValue = QString()); + + /// Set a string value for a key + void setValue(const QString &key, const QString &value); + +Q_SIGNALS: + // Property change signals + void groupKeyChanged(); + +protected: + QSettings *settings_; + QString groupKey_; + O0SimpleCrypt crypt_; +}; + +#endif // O0SETTINGSSTORE_H diff --git a/resources/tomboynotes/o2/o0simplecrypt.h b/resources/tomboynotes/o2/o0simplecrypt.h new file mode 100644 index 000000000..b28c210cf --- /dev/null +++ b/resources/tomboynotes/o2/o0simplecrypt.h @@ -0,0 +1,243 @@ +/* +Copyright (c) 2011, Andre Somers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SIMPLECRYPT_H +#define SIMPLECRYPT_H +#include +#include +#include + +/** + @short Simple encryption and decryption of strings and byte arrays + + This class provides a simple implementation of encryption and decryption + of strings and byte arrays. + + @warning The encryption provided by this class is NOT strong encryption. It may + help to shield things from curious eyes, but it will NOT stand up to someone + determined to break the encryption. Don't say you were not warned. + + The class uses a 64 bit key. Simply create an instance of the class, set the key, + and use the encryptToString() method to calculate an encrypted version of the input string. + To decrypt that string again, use an instance of SimpleCrypt initialized with + the same key, and call the decryptToString() method with the encrypted string. If the key + matches, the decrypted version of the string will be returned again. + + If you do not provide a key, or if something else is wrong, the encryption and + decryption function will return an empty string or will return a string containing nonsense. + lastError() will return a value indicating if the method was succesful, and if not, why not. + + SimpleCrypt is prepared for the case that the encryption and decryption + algorithm is changed in a later version, by prepending a version identifier to the cypertext. + */ +class O0SimpleCrypt +{ +public: + /** + CompressionMode describes if compression will be applied to the data to be + encrypted. + */ + enum CompressionMode { + CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */ + CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */ + CompressionNever /*!< Never apply compression. */ + }; + /** + IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data + or wrong decryption keys. + + Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This + increases the length of the resulting cypertext, but makes it possible to check if the plaintext + appears to be valid after decryption. + */ + enum IntegrityProtectionMode { + ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */ + ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */ + ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */ + }; + /** + Error describes the type of error that occured. + */ + enum Error { + ErrorNoError, /*!< No error occurred. */ + ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */ + ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */ + ErrorIntegrityFailed /*!< The integrity check of the data failed. Perhaps the wrong key was used. */ + }; + + /** + Constructor. + + Constructs a SimpleCrypt instance without a valid key set on it. + */ + O0SimpleCrypt(); + /** + Constructor. + + Constructs a SimpleCrypt instance and initializes it with the given @arg key. + */ + explicit O0SimpleCrypt(quint64 key); + + /** + (Re-) initializes the key with the given @arg key. + */ + void setKey(quint64 key); + /** + Returns true if SimpleCrypt has been initialized with a key. + */ + bool hasKey() const + { + return !m_keyParts.isEmpty(); + } + + /** + Sets the compression mode to use when encrypting data. The default mode is Auto. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setCompressionMode(CompressionMode mode) + { + m_compressionMode = mode; + } + /** + Returns the CompressionMode that is currently in use. + */ + CompressionMode compressionMode() const + { + return m_compressionMode; + } + + /** + Sets the integrity mode to use when encrypting data. The default mode is Checksum. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setIntegrityProtectionMode(IntegrityProtectionMode mode) + { + m_protectionMode = mode; + } + /** + Returns the IntegrityProtectionMode that is currently in use. + */ + IntegrityProtectionMode integrityProtectionMode() const + { + return m_protectionMode; + } + + /** + Returns the last error that occurred. + */ + Error lastError() const + { + return m_lastError; + } + + /** + Encrypts the @arg plaintext string with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the string, so it can be stored easily in a text format. + */ + QString encryptToString(const QString &plaintext) ; + /** + Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the encryption, so it can be stored easily in a text format. + */ + QString encryptToString(QByteArray plaintext) ; + /** + Encrypts the @arg plaintext string with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(const QString &plaintext) ; + /** + Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(QByteArray plaintext) ; + + /** + Decrypts a cyphertext string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(const QString &cyphertext) ; + /** + Decrypts a cyphertext string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(const QString &cyphertext) ; + /** + Decrypts a cyphertext binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(QByteArray cypher) ; + /** + Decrypts a cyphertext binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(QByteArray cypher) ; + + //enum to describe options that have been used for the encryption. Currently only one, but + //that only leaves room for future extensions like adding a cryptographic hash... + enum CryptoFlag {CryptoFlagNone = 0, + CryptoFlagCompression = 0x01, + CryptoFlagChecksum = 0x02, + CryptoFlagHash = 0x04 + }; + Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag) +private: + + void splitKey(); + + quint64 m_key; + QVector m_keyParts; + CompressionMode m_compressionMode; + IntegrityProtectionMode m_protectionMode; + Error m_lastError; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(O0SimpleCrypt::CryptoFlags) + +#endif // SimpleCrypt_H diff --git a/resources/tomboynotes/o2/o1.cpp b/resources/tomboynotes/o2/o1.cpp new file mode 100644 index 000000000..67660fc14 --- /dev/null +++ b/resources/tomboynotes/o2/o1.cpp @@ -0,0 +1,410 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 +#include +#endif + +#if QT_VERSION >= 0x050100 +#include +#endif + +#include "o2/o1.h" +#include "o2/o2replyserver.h" +#include "o2/o0globals.h" +#include "o2/o0settingsstore.h" + +O1::O1(QObject *parent): O0BaseAuth(parent) +{ + setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); + manager_ = new QNetworkAccessManager(this); + replyServer_ = new O2ReplyServer(this); + qRegisterMetaType("QNetworkReply::NetworkError"); + connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + setCallbackUrl(O2_CALLBACK_URL); +} + +QUrl O1::requestTokenUrl() +{ + return requestTokenUrl_; +} + +void O1::setRequestTokenUrl(const QUrl &v) +{ + requestTokenUrl_ = v; + Q_EMIT requestTokenUrlChanged(); +} + +QList O1::requestParameters() +{ + return requestParameters_; +} + +void O1::setRequestParameters(const QList &v) +{ + requestParameters_ = v; +} + +QString O1::callbackUrl() +{ + return callbackUrl_; +} + +void O1::setCallbackUrl(const QString &v) +{ + callbackUrl_ = v; +} + +QUrl O1::authorizeUrl() +{ + return authorizeUrl_; +} + +void O1::setAuthorizeUrl(const QUrl &value) +{ + authorizeUrl_ = value; + Q_EMIT authorizeUrlChanged(); +} + +QUrl O1::accessTokenUrl() +{ + return accessTokenUrl_; +} + +void O1::setAccessTokenUrl(const QUrl &value) +{ + accessTokenUrl_ = value; + Q_EMIT accessTokenUrlChanged(); +} + +QString O1::signatureMethod() +{ + return signatureMethod_; +} + +void O1::setSignatureMethod(const QString &value) +{ + qDebug() << "O1::setSignatureMethod: " << value; + signatureMethod_ = value; +} + +void O1::unlink() +{ + qDebug() << "O1::unlink"; + setLinked(false); + setToken(""); + setTokenSecret(""); + setExtraTokens(QVariantMap()); + Q_EMIT linkingSucceeded(); +} + +#if QT_VERSION < 0x050100 +/// Calculate the HMAC variant of SHA1 hash. +/// @author http://qt-project.org/wiki/HMAC-SHA1. +/// @copyright Creative Commons Attribution-ShareAlike 2.5 Generic. +static QByteArray hmacSha1(QByteArray key, QByteArray baseString) +{ + int blockSize = 64; + if (key.length() > blockSize) { + key = QCryptographicHash::hash(key, QCryptographicHash::Sha1); + } + QByteArray innerPadding(blockSize, char(0x36)); + QByteArray outerPadding(blockSize, char(0x5c)); + for (int i = 0; i < key.length(); i++) { + innerPadding[i] = innerPadding[i] ^ key.at(i); + outerPadding[i] = outerPadding[i] ^ key.at(i); + } + QByteArray total = outerPadding; + QByteArray part = innerPadding; + part.append(baseString); + total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1)); + QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1); + return hashed.toBase64(); +} +#endif + +/// Get HTTP operation name. +static QString getOperationName(QNetworkAccessManager::Operation op) +{ + switch (op) { + case QNetworkAccessManager::GetOperation: return "GET"; + case QNetworkAccessManager::PostOperation: return "POST"; + case QNetworkAccessManager::PutOperation: return "PUT"; + case QNetworkAccessManager::DeleteOperation: return "DEL"; + default: return ""; + } +} + +/// Build a concatenated/percent-encoded string from a list of headers. +QByteArray O1::encodeHeaders(const QList &headers) +{ + return QUrl::toPercentEncoding(createQueryParameters(headers)); +} + +/// Build a base string for signing. +QByteArray O1::getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) +{ + QByteArray base; + + // Initialize base string with the operation name (e.g. "GET") and the base URL + base.append(getOperationName(op).toUtf8() + "&"); + base.append(QUrl::toPercentEncoding(url.toString(QUrl::RemoveQuery)) + "&"); + + // Append a sorted+encoded list of all request parameters to the base string + QList headers(oauthParams); + headers.append(otherParams); + qSort(headers); + base.append(encodeHeaders(headers)); + + return base; +} + +QByteArray O1::sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) +{ + QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op); + QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret); +#if QT_VERSION >= 0x050100 + return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha1).toBase64(); +#else + return hmacSha1(secret, baseString); +#endif +} + +QByteArray O1::buildAuthorizationHeader(const QList &oauthParams) +{ + bool first = true; + QByteArray ret("OAuth "); + QList headers(oauthParams); + qSort(headers); + foreach (O0RequestParameter h, headers) { + if (first) { + first = false; + } else { + ret.append(","); + } + ret.append(h.name); + ret.append("=\""); + ret.append(QUrl::toPercentEncoding(h.value)); + ret.append("\""); + } + return ret; +} + +QByteArray O1::generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) +{ + QByteArray signature; + if (signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA1) { + signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret()); + } else if (signatureMethod() == O2_SIGNATURE_TYPE_PLAINTEXT) { + signature = clientSecret().toLatin1() + "&" + tokenSecret().toLatin1(); + } + return signature; +} + +void O1::link() +{ + qDebug() << "O1::link"; + if (linked()) { + qDebug() << "O1::link: Linked already"; + Q_EMIT linkingSucceeded(); + return; + } + + setLinked(false); + setToken(""); + setTokenSecret(""); + setExtraTokens(QVariantMap()); + + // Start reply server + replyServer_->listen(QHostAddress::Any, localPort()); + + // Get any query parameters for the request + QUrlQuery requestData; + O0RequestParameter param("", ""); + foreach (param, requestParameters()) { + requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value))); + } + + // Get the request url and add parameters + QUrl requestUrl = requestTokenUrl(); + requestUrl.setQuery(requestData); + + // Create request + QNetworkRequest request(requestUrl); + + // Create initial token request + QList headers; + headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1())); + headers.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); + headers.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); + headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + headers.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); + headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); + headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, requestParameters(), QNetworkAccessManager::PostOperation))); + + // Clear request token + requestToken_.clear(); + requestTokenSecret_.clear(); + + // Post request + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(headers)); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QNetworkReply *reply = manager_->post(request, QByteArray()); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished())); +} + +void O1::onTokenRequestError(QNetworkReply::NetworkError error) +{ + QNetworkReply *reply = qobject_cast(sender()); + qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll(); + Q_EMIT linkingFailed(); +} + +void O1::onTokenRequestFinished() +{ + qDebug() << "O1::onTokenRequestFinished"; + QNetworkReply *reply = qobject_cast(sender()); + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "O1::onTokenRequestFinished: " << reply->errorString(); + return; + } + + // Get request token and secret + QByteArray data = reply->readAll(); + QMap response = parseResponse(data); + requestToken_ = response.value(O2_OAUTH_TOKEN, ""); + requestTokenSecret_ = response.value(O2_OAUTH_TOKEN_SECRET, ""); + setToken(requestToken_); + setTokenSecret(requestTokenSecret_); + + // Checking for "oauth_callback_confirmed" is present and set to true + QString oAuthCbConfirmed = response.value(O2_OAUTH_CALLBACK_CONFIRMED, "false"); + if (requestToken_.isEmpty() || requestTokenSecret_.isEmpty() || (oAuthCbConfirmed == "false")) { + qWarning() << "O1::onTokenRequestFinished: No oauth_token, oauth_token_secret or oauth_callback_confirmed in response :" << data; + Q_EMIT linkingFailed(); + return; + } + + // Continue authorization flow in the browser + QUrl url(authorizeUrl()); +#if QT_VERSION < 0x050000 + url.addQueryItem(O2_OAUTH_TOKEN, requestToken_); + url.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1()); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH_TOKEN, requestToken_); + query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1()); + url.setQuery(query); +#endif + Q_EMIT openBrowser(url); +} + +void O1::onVerificationReceived(QMap params) +{ + qDebug() << "O1::onVerificationReceived"; + Q_EMIT closeBrowser(); + verifier_ = params.value(O2_OAUTH_VERFIER, ""); + if (params.value(O2_OAUTH_TOKEN) == requestToken_) { + // Exchange request token for access token + exchangeToken(); + } else { + qWarning() << "O1::onVerificationReceived: oauth_token missing or doesn't match"; + Q_EMIT linkingFailed(); + } +} + +void O1::exchangeToken() +{ + qDebug() << "O1::exchangeToken"; + + // Create token exchange request + QNetworkRequest request(accessTokenUrl()); + QList oauthParams; + oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); + oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); + oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList(), QNetworkAccessManager::PostOperation))); + + // Post request + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QNetworkReply *reply = manager_->post(request, QByteArray()); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); +} + +void O1::onTokenExchangeError(QNetworkReply::NetworkError error) +{ + QNetworkReply *reply = qobject_cast(sender()); + qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll(); + Q_EMIT linkingFailed(); +} + +void O1::onTokenExchangeFinished() +{ + qDebug() << "O1::onTokenExchangeFinished"; + + QNetworkReply *reply = qobject_cast(sender()); + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "O1::onTokenExchangeFinished: " << reply->errorString(); + return; + } + + // Get access token and secret + QByteArray data = reply->readAll(); + QMap response = parseResponse(data); + if (response.contains(O2_OAUTH_TOKEN) && response.contains(O2_OAUTH_TOKEN_SECRET)) { + setToken(response.take(O2_OAUTH_TOKEN)); + setTokenSecret(response.take(O2_OAUTH_TOKEN_SECRET)); + // Set extra tokens if any + if (!response.isEmpty()) { + QVariantMap extraTokens; + foreach (QString key, response.keys()) { + extraTokens.insert(key, response.value(key)); + } + setExtraTokens(extraTokens); + } + setLinked(true); + Q_EMIT linkingSucceeded(); + } else { + qWarning() << "O1::onTokenExchangeFinished: oauth_token or oauth_token_secret missing from response" << data; + Q_EMIT linkingFailed(); + } +} + +QMap O1::parseResponse(const QByteArray &response) +{ + QMap ret; + foreach (QByteArray param, response.split('&')) { + QList kv = param.split('='); + if (kv.length() == 2) { + ret.insert(QUrl::fromPercentEncoding(kv[0]), QUrl::fromPercentEncoding(kv[1])); + } + } + return ret; +} + +QByteArray O1::nonce() +{ + static bool firstTime = true; + if (firstTime) { + firstTime = false; + qsrand(QTime::currentTime().msec()); + } + QString u = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); + u.append(QString::number(qrand())); + return u.toLatin1(); +} diff --git a/resources/tomboynotes/o2/o1.h b/resources/tomboynotes/o2/o1.h new file mode 100644 index 000000000..2330ca740 --- /dev/null +++ b/resources/tomboynotes/o2/o1.h @@ -0,0 +1,125 @@ +#ifndef O1_H +#define O1_H + +#include +#include +#include + +#include "o2/o0baseauth.h" + +class O2ReplyServer; + +/// Simple OAuth 1.0 authenticator. +class O1: public O0BaseAuth +{ + Q_OBJECT + +public: + /// Signature method + Q_PROPERTY(QString signatureMethod READ signatureMethod WRITE setSignatureMethod NOTIFY signatureMethodChanged) + QString signatureMethod(); + void setSignatureMethod(const QString &value); + + /// Token request URL. + Q_PROPERTY(QUrl requestTokenUrl READ requestTokenUrl WRITE setRequestTokenUrl NOTIFY requestTokenUrlChanged) + QUrl requestTokenUrl(); + void setRequestTokenUrl(const QUrl &value); + + /// Parameters to pass with request URL. + Q_PROPERTY(QList requestParameters READ requestParameters WRITE setRequestParameters); + QList requestParameters(); + void setRequestParameters(const QList &value); + + /// Callback URL. + /// It should contain a `%1` place marker, to be replaced by `O0BaseAuth::localPort()`. + /// Defaults to `O2_CALLBACK_URL`. + Q_PROPERTY(QString callbackUrl READ callbackUrl WRITE setCallbackUrl) + QString callbackUrl(); + void setCallbackUrl(const QString &value); + + /// Authorization URL. + Q_PROPERTY(QUrl authorizeUrl READ authorizeUrl WRITE setAuthorizeUrl NOTIFY authorizeUrlChanged) + QUrl authorizeUrl(); + void setAuthorizeUrl(const QUrl &value); + + /// Access token URL. + Q_PROPERTY(QUrl accessTokenUrl READ accessTokenUrl WRITE setAccessTokenUrl NOTIFY accessTokenUrlChanged) + QUrl accessTokenUrl(); + void setAccessTokenUrl(const QUrl &value); + + /// Constructor. + explicit O1(QObject *parent = 0); + + /// Parse a URL-encoded response string. + static QMap parseResponse(const QByteArray &response); + + /// Build the value of the "Authorization:" header. + static QByteArray buildAuthorizationHeader(const QList &oauthParams); + + /// Create unique bytes to prevent replay attacks. + static QByteArray nonce(); + + /// Generate signature string depending on signature method type + QByteArray generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation); + + /// Calculate the HMAC-SHA1 signature of a request. + /// @param oauthParams OAuth parameters. + /// @param otherParams Other parameters participating in signing. + /// @param URL Request URL. May contain query parameters, but they will not be used for signing. + /// @param op HTTP operation. + /// @param consumerSecret Consumer (application) secret. + /// @param tokenSecret Authorization token secret (empty if not yet available). + /// @return Signature that can be used as the value of the "oauth_signature" parameter. + static QByteArray sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret); + + /// Build a base string for signing. + static QByteArray getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op); + + /// Build a concatenated/percent-encoded string from a list of headers. + static QByteArray encodeHeaders(const QList &headers); + +public Q_SLOTS: + /// Authenticate. + Q_INVOKABLE virtual void link(); + + /// De-authenticate. + Q_INVOKABLE virtual void unlink(); + +Q_SIGNALS: + void requestTokenUrlChanged(); + void authorizeUrlChanged(); + void accessTokenUrlChanged(); + void signatureMethodChanged(); + +protected Q_SLOTS: + /// Handle verification received from the reply server. + virtual void onVerificationReceived(QMap params); + + /// Handle token request error. + virtual void onTokenRequestError(QNetworkReply::NetworkError error); + + /// Handle token request finished. + virtual void onTokenRequestFinished(); + + /// Handle token exchange error. + void onTokenExchangeError(QNetworkReply::NetworkError error); + + /// Handle token exchange finished. + void onTokenExchangeFinished(); + +protected: + /// Exchange temporary token to authentication token + void exchangeToken(); + + QUrl requestUrl_; + QList requestParameters_; + QString callbackUrl_; + QUrl tokenUrl_; + QUrl refreshTokenUrl_; + QString verifier_; + QString signatureMethod_; + QNetworkAccessManager *manager_; + O2ReplyServer *replyServer_; +}; + +#endif // O1_H diff --git a/resources/tomboynotes/o2/o1requestor.cpp b/resources/tomboynotes/o2/o1requestor.cpp new file mode 100644 index 000000000..f3e049ae8 --- /dev/null +++ b/resources/tomboynotes/o2/o1requestor.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +#include "o2/o1requestor.h" +#include "o2/o1timedreply.h" +#include "o2/o0globals.h" + +O1Requestor::O1Requestor(QNetworkAccessManager *manager, O1 *authenticator, QObject *parent): QObject(parent) +{ + manager_ = manager; + authenticator_ = authenticator; +} + +QNetworkReply *O1Requestor::get(const QNetworkRequest &req, const QList &signingParameters) +{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::GetOperation); + return addTimer(manager_->get(request)); +} + +QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) +{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PostOperation); + return addTimer(manager_->post(request, data)); +} + +QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, QHttpMultiPart *multiPart) +{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PostOperation); + return addTimer(manager_->post(request, multiPart)); +} + +QNetworkReply *O1Requestor::put(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) +{ + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PutOperation); + return addTimer(manager_->put(request, data)); +} + +QNetworkReply *O1Requestor::addTimer(QNetworkReply *reply) +{ + (void)new O1TimedReply(reply); + return reply; +} + +QNetworkRequest O1Requestor::setup(const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) +{ + // Collect OAuth parameters + QList oauthParams; + oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, authenticator_->clientId().toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); + oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, authenticator_->token().toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, authenticator_->signatureMethod().toLatin1())); + oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, O1::nonce())); + oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + + // Add signature parameter + oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, authenticator_->generateSignature(oauthParams, req, signingParameters, operation))); + + // Return a copy of the original request with authorization header set + QNetworkRequest request(req); + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, O1::buildAuthorizationHeader(oauthParams)); + return request; +} diff --git a/resources/tomboynotes/o2/o1requestor.h b/resources/tomboynotes/o2/o1requestor.h new file mode 100644 index 000000000..0be21bc80 --- /dev/null +++ b/resources/tomboynotes/o2/o1requestor.h @@ -0,0 +1,61 @@ +#ifndef O1REQUESTOR_H +#define O1REQUESTOR_H + +#include +#include +#include + +#include "o2/o1.h" + +class QNetworkAccessManager; +class QNetworkReply; +class O1; + +/// Makes authenticated requests using OAuth 1.0. +class O1Requestor: public QObject +{ + Q_OBJECT + +public: + explicit O1Requestor(QNetworkAccessManager *manager, O1 *authenticator, QObject *parent = 0); + +public Q_SLOTS: + /// Make a GET request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @return Reply. + QNetworkReply *get(const QNetworkRequest &req, const QList &signingParameters); + + /// Make a POST request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @param data Request payload. + /// @return Reply. + QNetworkReply *post(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data); + + /// Make a POST request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @param multiPart HTTPMultiPart. + /// @return Reply. + QNetworkReply *post(const QNetworkRequest &req, const QList &signingParameters, QHttpMultiPart *multiPart); + + /// Make a PUT request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @param data Request payload. + /// @return Reply. + QNetworkReply *put(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data); + +protected: + /// Return new request based on the original, with the "Authentication:" header added. + QNetworkRequest setup(const QNetworkRequest &request, const QList &signingParameters, QNetworkAccessManager::Operation operation); + + /// Augment reply with a timer. + QNetworkReply *addTimer(QNetworkReply *reply); + + QNetworkAccessManager *manager_; + O1 *authenticator_; +}; + +#endif // O1REQUESTOR_H diff --git a/resources/tomboynotes/o2/o1timedreply.cpp b/resources/tomboynotes/o2/o1timedreply.cpp new file mode 100644 index 000000000..4685be2e6 --- /dev/null +++ b/resources/tomboynotes/o2/o1timedreply.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "o2/o1timedreply.h" + +O1TimedReply::O1TimedReply(QNetworkReply *parent, int pTimeout): QTimer(parent) +{ + setSingleShot(true); + setInterval(pTimeout); + connect(this, SIGNAL(timeout()), this, SLOT(onTimeout())); + connect(parent, SIGNAL(finished()), this, SLOT(onFinished())); +} + +void O1TimedReply::onFinished() +{ + stop(); + Q_EMIT finished(); +} + +void O1TimedReply::onTimeout() +{ + Q_EMIT error(QNetworkReply::TimeoutError); +} diff --git a/resources/tomboynotes/o2/o1timedreply.h b/resources/tomboynotes/o2/o1timedreply.h new file mode 100644 index 000000000..ddb456577 --- /dev/null +++ b/resources/tomboynotes/o2/o1timedreply.h @@ -0,0 +1,26 @@ +#ifndef O1TIMEDREPLY_H +#define O1TIMEDREPLY_H + +#include +#include + +/// A timer connected to a network reply. +class O1TimedReply: public QTimer +{ + Q_OBJECT + +public: + explicit O1TimedReply(QNetworkReply *parent, int pTimeout = 60 * 1000); + +Q_SIGNALS: + /// Emitted when we have timed out waiting for the network reply. + void error(QNetworkReply::NetworkError); + /// Emitted when the network reply has responded. + void finished(); + +private Q_SLOTS: + void onFinished(); + void onTimeout(); +}; + +#endif diff --git a/resources/tomboynotes/o2/o2.cpp b/resources/tomboynotes/o2/o2.cpp new file mode 100644 index 000000000..ec1799d0d --- /dev/null +++ b/resources/tomboynotes/o2/o2.cpp @@ -0,0 +1,458 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 +#include +#include +#include +#else +#include +#include +#endif + +#include "o2.h" +#include "o2replyserver.h" +#include "o0globals.h" +#include "o0settingsstore.h" + +/// Parse JSON data into a QVariantMap +static QVariantMap parseTokenResponse(const QByteArray &data) +{ +#if QT_VERSION >= 0x050000 + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) { + qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString(); + return QVariantMap(); + } + + if (!doc.isObject()) { + qWarning() << "parseTokenResponse: Token response is not an object"; + return QVariantMap(); + } + + return doc.object().toVariantMap(); +#else + QScriptEngine engine; + QScriptValue value = engine.evaluate("(" + QString(data) + ")"); + QScriptValueIterator it(value); + QVariantMap map; + + while (it.hasNext()) { + it.next(); + map.insert(it.name(), it.value().toVariant()); + } + + return map; +#endif +} + +/// Add query parameters to a query +static void addQueryParametersToUrl(QUrl &url, QList > parameters) +{ +#if QT_VERSION < 0x050000 + url.setQueryItems(parameters); +#else + QUrlQuery query(url); + query.setQueryItems(parameters); + url.setQuery(query); +#endif +} + +O2::O2(QObject *parent): O0BaseAuth(parent) +{ + manager_ = new QNetworkAccessManager(this); + replyServer_ = new O2ReplyServer(this); + grantFlow_ = GrantFlowAuthorizationCode; + localhostPolicy_ = QString(O2_CALLBACK_URL); + qRegisterMetaType("QNetworkReply::NetworkError"); + connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); +} + +O2::GrantFlow O2::grantFlow() +{ + return grantFlow_; +} + +void O2::setGrantFlow(O2::GrantFlow value) +{ + grantFlow_ = value; + Q_EMIT grantFlowChanged(); +} + +QString O2::username() +{ + return username_; +} + +void O2::setUsername(const QString &value) +{ + username_ = value; + Q_EMIT usernameChanged(); +} + +QString O2::password() +{ + return password_; +} + +void O2::setPassword(const QString &value) +{ + password_ = value; + Q_EMIT passwordChanged(); +} + +QString O2::scope() +{ + return scope_; +} + +void O2::setScope(const QString &value) +{ + scope_ = value; + Q_EMIT scopeChanged(); +} + +QString O2::requestUrl() +{ + return requestUrl_.toString(); +} + +void O2::setRequestUrl(const QString &value) +{ + requestUrl_ = value; + Q_EMIT requestUrlChanged(); +} + +QString O2::tokenUrl() +{ + return tokenUrl_.toString(); +} + +void O2::setTokenUrl(const QString &value) +{ + tokenUrl_ = value; + Q_EMIT tokenUrlChanged(); +} + +QString O2::refreshTokenUrl() +{ + return refreshTokenUrl_.toString(); +} + +void O2::setRefreshTokenUrl(const QString &value) +{ + refreshTokenUrl_ = value; + Q_EMIT refreshTokenUrlChanged(); +} + +void O2::link() +{ + qDebug() << "O2::link"; + + if (linked()) { + qDebug() << "O2::link: Linked already"; + Q_EMIT linkingSucceeded(); + return; + } + + setLinked(false); + setToken(""); + setTokenSecret(""); + setExtraTokens(QVariantMap()); + setRefreshToken(QString()); + setExpires(0); + + if (grantFlow_ == GrantFlowAuthorizationCode) { + // Start listening to authentication replies + replyServer_->listen(QHostAddress::Any, localPort_); + + // Save redirect URI, as we have to reuse it when requesting the access token + redirectUri_ = localhostPolicy_.arg(replyServer_->serverPort()); + + // Assemble intial authentication URL + QList > parameters; + parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE), (grantFlow_ == GrantFlowAuthorizationCode) ? QString(O2_OAUTH2_GRANT_TYPE_CODE) : QString(O2_OAUTH2_GRANT_TYPE_TOKEN))); + parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_)); + parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_)); + parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_)); + parameters.append(qMakePair(QString(O2_OAUTH2_API_KEY), apiKey_)); + + // Show authentication URL with a web browser + QUrl url(requestUrl_); + addQueryParametersToUrl(url, parameters); + qDebug() << "O2::link: Emit openBrowser" << url.toString(); + Q_EMIT openBrowser(url); + } else if (grantFlow_ == GrantFlowResourceOwnerPasswordCredentials) { + QList parameters; + parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_SECRET, clientSecret_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_USERNAME, username_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_PASSWORD, password_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_GRANT_TYPE_PASSWORD)); + parameters.append(O0RequestParameter(O2_OAUTH2_SCOPE, scope_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_API_KEY, apiKey_.toUtf8())); + QByteArray payload = O0BaseAuth::createQueryParameters(parameters); + + QUrl url(tokenUrl_); + QNetworkRequest tokenRequest(url); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QNetworkReply *tokenReply = manager_->post(tokenRequest, payload); + + connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); + connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + } +} + +void O2::unlink() +{ + qDebug() << "O2::unlink"; + setLinked(false); + setToken(QString()); + setRefreshToken(QString()); + setExpires(0); + setExtraTokens(QVariantMap()); + Q_EMIT linkingSucceeded(); +} + +void O2::onVerificationReceived(const QMap response) +{ + qDebug() << "O2::onVerificationReceived:" << response; + qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()"; + Q_EMIT closeBrowser(); + + if (response.contains("error")) { + qWarning() << "O2::onVerificationReceived: Verification failed: " << response; + Q_EMIT linkingFailed(); + return; + } + + if (grantFlow_ == GrantFlowAuthorizationCode) { + // Save access code + setCode(response.value(QString(O2_OAUTH2_GRANT_TYPE_CODE))); + + // Exchange access code for access/refresh tokens + QString query; + if (!apiKey_.isEmpty()) { + query = QString("?" + QString(O2_OAUTH2_API_KEY) + "=" + apiKey_); + } + QNetworkRequest tokenRequest(QUrl(tokenUrl_.toString() + query)); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QMap parameters; + parameters.insert(O2_OAUTH2_GRANT_TYPE_CODE, code()); + parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_); + parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_); + parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE); + QByteArray data = buildRequestBody(parameters); + QNetworkReply *tokenReply = manager_->post(tokenRequest, data); + timedReplies_.add(tokenReply); + connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); + connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + } else { + setToken(response.value(O2_OAUTH2_ACCESS_TOKEN)); + setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN)); + } +} + +QString O2::code() +{ + QString key = QString(O2_KEY_CODE).arg(clientId_); + return store_->value(key); +} + +void O2::setCode(const QString &c) +{ + QString key = QString(O2_KEY_CODE).arg(clientId_); + store_->setValue(key, c); +} + +void O2::onTokenReplyFinished() +{ + qDebug() << "O2::onTokenReplyFinished"; + QNetworkReply *tokenReply = qobject_cast(sender()); + if (tokenReply->error() == QNetworkReply::NoError) { + QByteArray replyData = tokenReply->readAll(); + QVariantMap tokens = parseTokenResponse(replyData); + + // Check for mandatory tokens + if (tokens.contains(O2_OAUTH2_ACCESS_TOKEN)) { + setToken(tokens.take(O2_OAUTH2_ACCESS_TOKEN).toString()); + bool ok = false; + int expiresIn = tokens.take(O2_OAUTH2_EXPIRES_IN).toInt(&ok); + if (ok) { + qDebug() << "O2::onTokenReplyFinished: Token expires in" << expiresIn << "seconds"; + setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn); + } + setRefreshToken(tokens.take(O2_OAUTH2_REFRESH_TOKEN).toString()); + setExtraTokens(tokens); + timedReplies_.remove(tokenReply); + setLinked(true); + Q_EMIT linkingSucceeded(); + } else { + qWarning() << "O2::onTokenReplyFinished: oauth_token missing from response" << replyData; + Q_EMIT linkingFailed(); + } + } + tokenReply->deleteLater(); +} + +void O2::onTokenReplyError(QNetworkReply::NetworkError error) +{ + QNetworkReply *tokenReply = qobject_cast(sender()); + qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString(); + qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll(); + setToken(QString()); + setRefreshToken(QString()); + timedReplies_.remove(tokenReply); + Q_EMIT linkingFailed(); +} + +QByteArray O2::buildRequestBody(const QMap ¶meters) +{ + QByteArray body; + bool first = true; + foreach (QString key, parameters.keys()) { + if (first) { + first = false; + } else { + body.append("&"); + } + QString value = parameters.value(key); + body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value)); + } + return body; +} + +int O2::expires() +{ + QString key = QString(O2_KEY_EXPIRES).arg(clientId_); + return store_->value(key).toInt(); +} + +void O2::setExpires(int v) +{ + QString key = QString(O2_KEY_EXPIRES).arg(clientId_); + store_->setValue(key, QString::number(v)); +} + +QString O2::refreshToken() +{ + QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_); + return store_->value(key); +} + +void O2::setRefreshToken(const QString &v) +{ + qDebug() << "O2::setRefreshToken" << v.left(4) << "..."; + QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_); + store_->setValue(key, v); +} + +void O2::refresh() +{ + qDebug() << "O2::refresh: Token: ..." << refreshToken().right(7); + + if (refreshToken().isEmpty()) { + qWarning() << "O2::refresh: No refresh token"; + onRefreshError(QNetworkReply::AuthenticationRequiredError); + return; + } + if (refreshTokenUrl_.isEmpty()) { + qWarning() << "O2::refresh: Refresh token URL not set"; + onRefreshError(QNetworkReply::AuthenticationRequiredError); + return; + } + + QNetworkRequest refreshRequest(refreshTokenUrl_); + refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QMap parameters; + parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_); + parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + parameters.insert(O2_OAUTH2_REFRESH_TOKEN, refreshToken()); + parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_REFRESH_TOKEN); + + QByteArray data = buildRequestBody(parameters); + QNetworkReply *refreshReply = manager_->post(refreshRequest, data); + timedReplies_.add(refreshReply); + connect(refreshReply, SIGNAL(finished()), this, SLOT(onRefreshFinished()), Qt::QueuedConnection); + connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +} + +void O2::onRefreshFinished() +{ + QNetworkReply *refreshReply = qobject_cast(sender()); + qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); + if (refreshReply->error() == QNetworkReply::NoError) { + QByteArray reply = refreshReply->readAll(); + QVariantMap tokens = parseTokenResponse(reply); + setToken(tokens.value(O2_OAUTH2_ACCESS_TOKEN).toString()); + setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt()); + setRefreshToken(tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString()); + timedReplies_.remove(refreshReply); + setLinked(true); + Q_EMIT linkingSucceeded(); + Q_EMIT refreshFinished(QNetworkReply::NoError); + qDebug() << " New token expires in" << expires() << "seconds"; + } + refreshReply->deleteLater(); +} + +void O2::onRefreshError(QNetworkReply::NetworkError error) +{ + QNetworkReply *refreshReply = qobject_cast(sender()); + qWarning() << "O2::onRefreshError: " << error; + unlink(); + timedReplies_.remove(refreshReply); + Q_EMIT refreshFinished(error); +} + +QString O2::localhostPolicy() const +{ + return localhostPolicy_; +} + +void O2::setLocalhostPolicy(const QString &value) +{ + localhostPolicy_ = value; +} + +QString O2::apiKey() +{ + return apiKey_; +} + +void O2::setApiKey(const QString &value) +{ + apiKey_ = value; +} + +QByteArray O2::replyContent() +{ + return replyServer_->replyContent(); +} + +void O2::setReplyContent(const QByteArray &value) +{ + replyServer_->setReplyContent(value); +} + +bool O2::ignoreSslErrors() +{ + return timedReplies_.ignoreSslErrors(); +} + +void O2::setIgnoreSslErrors(bool ignoreSslErrors) +{ + timedReplies_.setIgnoreSslErrors(ignoreSslErrors); +} diff --git a/resources/tomboynotes/o2/o2.h b/resources/tomboynotes/o2/o2.h new file mode 100644 index 000000000..3f7591b4d --- /dev/null +++ b/resources/tomboynotes/o2/o2.h @@ -0,0 +1,171 @@ +#ifndef O2_H +#define O2_H + +#include +#include +#include +#include + +#include "o0baseauth.h" +#include "o2reply.h" +#include "o0abstractstore.h" + +class O2ReplyServer; + +/// Simple OAuth2 authenticator. +class O2: public O0BaseAuth +{ + Q_OBJECT + Q_ENUMS(GrantFlow) + +public: + /// Authorization flow types. + enum GrantFlow { + GrantFlowAuthorizationCode, ///< @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1 + GrantFlowImplicit, ///< @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.2 + GrantFlowResourceOwnerPasswordCredentials, + }; + + /// Authorization flow. + Q_PROPERTY(GrantFlow grantFlow READ grantFlow WRITE setGrantFlow NOTIFY grantFlowChanged) + GrantFlow grantFlow(); + void setGrantFlow(GrantFlow value); + + /// Resource owner username. + /// O2 instances with the same (username, password) share the same "linked" and "token" properties. + Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged) + QString username(); + void setUsername(const QString &value); + + /// Resource owner password. + /// O2 instances with the same (username, password) share the same "linked" and "token" properties. + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + QString password(); + void setPassword(const QString &value); + + /// Scope of authentication. + Q_PROPERTY(QString scope READ scope WRITE setScope NOTIFY scopeChanged) + QString scope(); + void setScope(const QString &value); + + /// Localhost policy. By default it's value is http://127.0.0.1:%1/, however some services may + /// require the use of http://localhost:%1/ or any other value. + Q_PROPERTY(QString localhostPolicy READ localhostPolicy WRITE setLocalhostPolicy) + QString localhostPolicy() const; + void setLocalhostPolicy(const QString &value); + + /// API key. + Q_PROPERTY(QString apiKey READ apiKey WRITE setApiKey) + QString apiKey(); + void setApiKey(const QString &value); + + /// Page content on local host after successful oauth. + /// Provide it in case you do not want to close the browser, but display something + Q_PROPERTY(QByteArray replyContent READ replyContent WRITE setReplyContent) + QByteArray replyContent(); + void setReplyContent(const QByteArray &value); + + /// Allow ignoring SSL errors? + /// E.g. SurveyMonkey fails on Mac due to SSL error. Ignoring the error circumvents the problem + Q_PROPERTY(bool ignoreSslErrors READ ignoreSslErrors WRITE setIgnoreSslErrors) + bool ignoreSslErrors(); + void setIgnoreSslErrors(bool ignoreSslErrors); + + /// Request URL. + Q_PROPERTY(QString requestUrl READ requestUrl WRITE setRequestUrl NOTIFY requestUrlChanged) + QString requestUrl(); + void setRequestUrl(const QString &value); + + /// Token request URL. + Q_PROPERTY(QString tokenUrl READ tokenUrl WRITE setTokenUrl NOTIFY tokenUrlChanged) + QString tokenUrl(); + void setTokenUrl(const QString &value); + + /// Token refresh URL. + Q_PROPERTY(QString refreshTokenUrl READ refreshTokenUrl WRITE setRefreshTokenUrl NOTIFY refreshTokenUrlChanged) + QString refreshTokenUrl(); + void setRefreshTokenUrl(const QString &value); + +public: + /// Constructor. + /// @param parent Parent object. + explicit O2(QObject *parent = 0); + + /// Get authentication code. + QString code(); + + /// Get refresh token. + QString refreshToken(); + + /// Get token expiration time (seconds from Epoch). + int expires(); + +public Q_SLOTS: + /// Authenticate. + Q_INVOKABLE virtual void link(); + + /// De-authenticate. + Q_INVOKABLE virtual void unlink(); + + /// Refresh token. + Q_INVOKABLE void refresh(); + +Q_SIGNALS: + /// Emitted when a token refresh has been completed or failed. + void refreshFinished(QNetworkReply::NetworkError error); + + // Property change signals + void grantFlowChanged(); + void scopeChanged(); + void usernameChanged(); + void passwordChanged(); + void requestUrlChanged(); + void refreshTokenUrlChanged(); + void tokenUrlChanged(); + +protected Q_SLOTS: + /// Handle verification response. + virtual void onVerificationReceived(QMap); + + /// Handle completion of a token request. + virtual void onTokenReplyFinished(); + + /// Handle failure of a token request. + virtual void onTokenReplyError(QNetworkReply::NetworkError error); + + /// Handle completion of a refresh request. + virtual void onRefreshFinished(); + + /// Handle failure of a refresh request. + virtual void onRefreshError(QNetworkReply::NetworkError error); + +protected: + /// Build HTTP request body. + QByteArray buildRequestBody(const QMap ¶meters); + + /// Set authentication code. + void setCode(const QString &v); + + /// Set refresh token. + void setRefreshToken(const QString &v); + + /// Set token expiration time. + void setExpires(int v); + +protected: + QString username_; + QString password_; + QUrl requestUrl_; + QUrl tokenUrl_; + QUrl refreshTokenUrl_; + QString scope_; + QString code_; + QString localhostPolicy_; + QString apiKey_; + QNetworkAccessManager *manager_; + O2ReplyServer *replyServer_; + O2ReplyList timedReplies_; + GrantFlow grantFlow_; +}; + +#endif // O2_H diff --git a/resources/tomboynotes/o2/o2reply.cpp b/resources/tomboynotes/o2/o2reply.cpp new file mode 100644 index 000000000..db8517a48 --- /dev/null +++ b/resources/tomboynotes/o2/o2reply.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include "o2/o2reply.h" + +O2Reply::O2Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) +{ + setSingleShot(true); + connect(this, SIGNAL(error(QNetworkReply::NetworkError)), reply, SIGNAL(error(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(this, SIGNAL(timeout()), this, SLOT(onTimeOut()), Qt::QueuedConnection); + start(timeOut); +} + +void O2Reply::onTimeOut() +{ + Q_EMIT error(QNetworkReply::TimeoutError); +} + +O2ReplyList::~O2ReplyList() +{ + foreach (O2Reply *timedReply, replies_) { + delete timedReply; + } +} + +void O2ReplyList::add(QNetworkReply *reply) +{ + if (reply && ignoreSslErrors()) { + reply->ignoreSslErrors(); + } + add(new O2Reply(reply)); +} + +void O2ReplyList::add(O2Reply *reply) +{ + replies_.append(reply); +} + +void O2ReplyList::remove(QNetworkReply *reply) +{ + O2Reply *o2Reply = find(reply); + if (o2Reply) { + o2Reply->stop(); + (void)replies_.removeOne(o2Reply); + } +} + +O2Reply *O2ReplyList::find(QNetworkReply *reply) +{ + foreach (O2Reply *timedReply, replies_) { + if (timedReply->reply == reply) { + return timedReply; + } + } + return 0; +} + +bool O2ReplyList::ignoreSslErrors() +{ + return ignoreSslErrors_; +} + +void O2ReplyList::setIgnoreSslErrors(bool ignoreSslErrors) +{ + ignoreSslErrors_ = ignoreSslErrors; +} diff --git a/resources/tomboynotes/o2/o2reply.h b/resources/tomboynotes/o2/o2reply.h new file mode 100644 index 000000000..4bfcd5125 --- /dev/null +++ b/resources/tomboynotes/o2/o2reply.h @@ -0,0 +1,64 @@ +#ifndef O2TIMEDREPLYLIST_H +#define O2TIMEDREPLYLIST_H + +#include +#include +#include +#include +#include +#include + +/// A network request/reply pair that can time out. +class O2Reply: public QTimer +{ + Q_OBJECT + +public: + O2Reply(QNetworkReply *reply, int timeOut = 60 * 1000, QObject *parent = 0); + +Q_SIGNALS: + void error(QNetworkReply::NetworkError); + +public Q_SLOTS: + /// When time out occurs, the QNetworkReply's error() signal is triggered. + void onTimeOut(); + +public: + QNetworkReply *reply; +}; + +/// List of O2Replies. +class O2ReplyList +{ +public: + O2ReplyList() + { + ignoreSslErrors_ = false; + } + + /// Destructor. + /// Deletes all O2Reply instances in the list. + virtual ~O2ReplyList(); + + /// Create a new O2Reply from a QNetworkReply, and add it to this list. + void add(QNetworkReply *reply); + + /// Add an O2Reply to the list, while taking ownership of it. + void add(O2Reply *reply); + + /// Remove item from the list that corresponds to a QNetworkReply. + void remove(QNetworkReply *reply); + + /// Find an O2Reply in the list, corresponding to a QNetworkReply. + /// @return Matching O2Reply or NULL. + O2Reply *find(QNetworkReply *reply); + + bool ignoreSslErrors(); + void setIgnoreSslErrors(bool ignoreSslErrors); + +protected: + QList replies_; + bool ignoreSslErrors_; +}; + +#endif // O2TIMEDREPLYLIST_H diff --git a/resources/tomboynotes/o2/o2replyserver.cpp b/resources/tomboynotes/o2/o2replyserver.cpp new file mode 100644 index 000000000..b4c5da794 --- /dev/null +++ b/resources/tomboynotes/o2/o2replyserver.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2/o2replyserver.h" + +O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent) +{ + connect(this, SIGNAL(newConnection()), this, SLOT(onIncomingConnection())); + replyContent_ = ""; +} + +void O2ReplyServer::onIncomingConnection() +{ + QTcpSocket *socket = nextPendingConnection(); + connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection); + connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); +} + +void O2ReplyServer::onBytesReady() +{ + qDebug() << "O2ReplyServer::onBytesReady"; + QTcpSocket *socket = qobject_cast(sender()); + if (!socket) { + return; + } + QByteArray reply; + reply.append("HTTP/1.0 200 OK \r\n"); + reply.append("Content-Type: text/html; charset=\"utf-8\"\r\n"); + reply.append(QString("Content-Length: %1\r\n\r\n").arg(replyContent_.size())); + reply.append(replyContent_); + socket->write(reply); + + QByteArray data = socket->readAll(); + QMap queryParams = parseQueryParams(&data); + socket->disconnectFromHost(); + close(); + Q_EMIT verificationReceived(queryParams); +} + +QMap O2ReplyServer::parseQueryParams(QByteArray *data) +{ + qDebug() << "O2ReplyServer::parseQueryParams"; + + QString splitGetLine = QString(*data).split("\r\n").first(); + splitGetLine.remove("GET "); + splitGetLine.remove("HTTP/1.1"); + splitGetLine.remove("\r\n"); + splitGetLine.prepend("http://localhost"); + QUrl getTokenUrl(splitGetLine); + + QList< QPair > tokens; +#if QT_VERSION < 0x050000 + tokens = getTokenUrl.queryItems(); +#else + QUrlQuery query(getTokenUrl); + tokens = query.queryItems(); +#endif + QMultiMap queryParams; + QPair tokenPair; + foreach (tokenPair, tokens) { + // FIXME: We are decoding key and value again. This helps with Google OAuth, but is it mandated by the standard? + QString key = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.first.trimmed())); + QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed())); + queryParams.insert(key, value); + } + return queryParams; +} + +QByteArray O2ReplyServer::replyContent() +{ + return replyContent_; +} + +void O2ReplyServer::setReplyContent(const QByteArray &value) +{ + replyContent_ = value; +} diff --git a/resources/tomboynotes/o2/o2replyserver.h b/resources/tomboynotes/o2/o2replyserver.h new file mode 100644 index 000000000..f46412663 --- /dev/null +++ b/resources/tomboynotes/o2/o2replyserver.h @@ -0,0 +1,34 @@ +#ifndef O2REPLYSERVER_H +#define O2REPLYSERVER_H + +#include +#include +#include +#include + +/// HTTP server to process authentication response. +class O2ReplyServer: public QTcpServer +{ + Q_OBJECT + +public: + explicit O2ReplyServer(QObject *parent = 0); + + /// Page content on local host after successful oauth - in case you do not want to close the browser, but display something + Q_PROPERTY(QByteArray replyContent READ replyContent WRITE setReplyContent) + QByteArray replyContent(); + void setReplyContent(const QByteArray &value); + +Q_SIGNALS: + void verificationReceived(QMap); + +public Q_SLOTS: + void onIncomingConnection(); + void onBytesReady(); + QMap parseQueryParams(QByteArray *data); + +protected: + QByteArray replyContent_; +}; + +#endif // O2REPLYSERVER_H diff --git a/resources/tomboynotes/o2/o2requestor.cpp b/resources/tomboynotes/o2/o2requestor.cpp new file mode 100644 index 000000000..89248a5ff --- /dev/null +++ b/resources/tomboynotes/o2/o2requestor.cpp @@ -0,0 +1,205 @@ +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2/o2requestor.h" +#include "o2/o2.h" +#include "o2/o0globals.h" + +O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent): QObject(parent), reply_(NULL), status_(Idle) +{ + manager_ = manager; + authenticator_ = authenticator; + if (authenticator) { + timedReplies_.setIgnoreSslErrors(authenticator->ignoreSslErrors()); + } + qRegisterMetaType("QNetworkReply::NetworkError"); + connect(authenticator, SIGNAL(refreshFinished(QNetworkReply::NetworkError)), this, SLOT(onRefreshFinished(QNetworkReply::NetworkError)), Qt::QueuedConnection); +} + +O2Requestor::~O2Requestor() +{ +} + +int O2Requestor::get(const QNetworkRequest &req) +{ + if (-1 == setup(req, QNetworkAccessManager::GetOperation)) { + return -1; + } + reply_ = manager_->get(request_); + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + return id_; +} + +int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data) +{ + if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { + return -1; + } + data_ = data; + reply_ = manager_->post(request_, data_); + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data) +{ + if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { + return -1; + } + data_ = data; + reply_ = manager_->put(request_, data_); + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) +{ + if (status_ != Requesting) { + qWarning() << "O2Requestor::onRefreshFinished: No pending request"; + return; + } + if (QNetworkReply::NoError == error) { + QTimer::singleShot(100, this, SLOT(retry())); + } else { + error_ = error; + QTimer::singleShot(10, this, SLOT(finish())); + } +} + +void O2Requestor::onRequestFinished() +{ + QNetworkReply *senderReply = qobject_cast(sender()); + QNetworkReply::NetworkError error = senderReply->error(); + if (status_ == Idle) { + return; + } + if (reply_ != senderReply) { + return; + } + if (error == QNetworkReply::NoError) { + QTimer::singleShot(10, this, SLOT(finish())); + } +} + +void O2Requestor::onRequestError(QNetworkReply::NetworkError error) +{ + qWarning() << "O2Requestor::onRequestError: Error" << (int)error; + if (status_ == Idle) { + return; + } + if (reply_ != qobject_cast(sender())) { + return; + } + int httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + qWarning() << "O2Requestor::onRequestError: HTTP status" << httpStatus << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + qDebug() << reply_->readAll(); + if ((status_ == Requesting) && (httpStatus == 401)) { + // Call O2::refresh. Note the O2 instance might live in a different thread + if (QMetaObject::invokeMethod(authenticator_, "refresh")) { + return; + } + qCritical() << "O2Requestor::onRequestError: Invoking remote refresh failed"; + } + error_ = error; + QTimer::singleShot(10, this, SLOT(finish())); +} + +void O2Requestor::onUploadProgress(qint64 uploaded, qint64 total) +{ + if (status_ == Idle) { + qWarning() << "O2Requestor::onUploadProgress: No pending request"; + return; + } + if (reply_ != qobject_cast(sender())) { + return; + } + Q_EMIT uploadProgress(id_, uploaded, total); +} + +int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation) +{ + static int currentId; + QUrl url; + + if (status_ != Idle) { + qWarning() << "O2Requestor::setup: Another request pending"; + return -1; + } + + request_ = req; + operation_ = operation; + id_ = currentId++; + url_ = url = req.url(); +#if QT_VERSION < 0x050000 + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); +#endif + request_.setUrl(url); + status_ = Requesting; + error_ = QNetworkReply::NoError; + return id_; +} + +void O2Requestor::finish() +{ + QByteArray data; + if (status_ == Idle) { + qWarning() << "O2Requestor::finish: No pending request"; + return; + } + data = reply_->readAll(); + status_ = Idle; + timedReplies_.remove(reply_); + reply_->disconnect(this); + reply_->deleteLater(); + Q_EMIT finished(id_, error_, data); +} + +void O2Requestor::retry() +{ + if (status_ != Requesting) { + qWarning() << "O2Requestor::retry: No pending request"; + return; + } + timedReplies_.remove(reply_); + reply_->disconnect(this); + reply_->deleteLater(); + QUrl url = url_; +#if QT_VERSION < 0x050000 + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); +#endif + request_.setUrl(url); + status_ = ReRequesting; + switch (operation_) { + case QNetworkAccessManager::GetOperation: + reply_ = manager_->get(request_); + break; + case QNetworkAccessManager::PostOperation: + reply_ = manager_->post(request_, data_); + break; + default: + reply_ = manager_->put(request_, data_); + } + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); +} diff --git a/resources/tomboynotes/o2/o2requestor.h b/resources/tomboynotes/o2/o2requestor.h new file mode 100644 index 000000000..fef7fc0c8 --- /dev/null +++ b/resources/tomboynotes/o2/o2requestor.h @@ -0,0 +1,83 @@ +#ifndef O2REQUESTOR_H +#define O2REQUESTOR_H + +#include +#include +#include +#include +#include +#include + +#include "o2/o2reply.h" + +class O2; + +/// Makes authenticated requests. +class O2Requestor: public QObject +{ + Q_OBJECT + +public: + explicit O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent = 0); + ~O2Requestor(); + +public Q_SLOTS: + /// Make a GET request. + /// @return Request ID or -1 if there are too many requests in the queue. + int get(const QNetworkRequest &req); + + /// Make a POST request. + /// @return Request ID or -1 if there are too many requests in the queue. + int post(const QNetworkRequest &req, const QByteArray &data); + + /// Make a PUT request. + /// @return Request ID or -1 if there are too many requests in the queue. + int put(const QNetworkRequest &req, const QByteArray &data); + +Q_SIGNALS: + /// Emitted when a request has been completed or failed. + void finished(int id, QNetworkReply::NetworkError error, QByteArray data); + + /// Emitted when an upload has progressed. + void uploadProgress(int id, qint64 bytesSent, qint64 bytesTotal); + +protected Q_SLOTS: + /// Handle refresh completion. + void onRefreshFinished(QNetworkReply::NetworkError error); + + /// Handle request finished. + void onRequestFinished(); + + /// Handle request error. + void onRequestError(QNetworkReply::NetworkError error); + + /// Re-try request (after successful token refresh). + void retry(); + + /// Finish the request, Q_EMIT finished() signal. + void finish(); + + /// Handle upload progress. + void onUploadProgress(qint64 uploaded, qint64 total); + +protected: + int setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation); + + enum Status { + Idle, Requesting, ReRequesting + }; + + QNetworkAccessManager *manager_; + O2 *authenticator_; + QNetworkRequest request_; + QByteArray data_; + QNetworkReply *reply_; + Status status_; + int id_; + QNetworkAccessManager::Operation operation_; + QUrl url_; + O2ReplyList timedReplies_; + QNetworkReply::NetworkError error_; +}; + +#endif // O2REQUESTOR_H diff --git a/resources/tomboynotes/o2/o2simplecrypt.cpp b/resources/tomboynotes/o2/o2simplecrypt.cpp new file mode 100644 index 000000000..4a8584c5a --- /dev/null +++ b/resources/tomboynotes/o2/o2simplecrypt.cpp @@ -0,0 +1,255 @@ +/* +Copyright (c) 2011, Andre Somers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "o2/o0simplecrypt.h" +#include +#include +#include +#include +#include +#include + +O0SimpleCrypt::O0SimpleCrypt(): + m_key(0), + m_compressionMode(CompressionAuto), + m_protectionMode(ProtectionChecksum), + m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +} + +O0SimpleCrypt::O0SimpleCrypt(quint64 key): + m_key(key), + m_compressionMode(CompressionAuto), + m_protectionMode(ProtectionChecksum), + m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); + splitKey(); +} + +void O0SimpleCrypt::setKey(quint64 key) +{ + m_key = key; + splitKey(); +} + +void O0SimpleCrypt::splitKey() +{ + m_keyParts.clear(); + m_keyParts.resize(8); + for (int i = 0; i < 8; i++) { + quint64 part = m_key; + for (int j = i; j > 0; j--) { + part = part >> 8; + } + part = part & 0xff; + m_keyParts[i] = static_cast(part); + } +} + +QByteArray O0SimpleCrypt::encryptToByteArray(const QString &plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + return encryptToByteArray(plaintextArray); +} + +QByteArray O0SimpleCrypt::encryptToByteArray(QByteArray plaintext) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + QByteArray ba = plaintext; + + CryptoFlags flags = CryptoFlagNone; + if (m_compressionMode == CompressionAlways) { + ba = qCompress(ba, 9); //maximum compression + flags |= CryptoFlagCompression; + } else if (m_compressionMode == CompressionAuto) { + QByteArray compressed = qCompress(ba, 9); + if (compressed.count() < ba.count()) { + ba = compressed; + flags |= CryptoFlagCompression; + } + } + + QByteArray integrityProtection; + if (m_protectionMode == ProtectionChecksum) { + flags |= CryptoFlagChecksum; + QDataStream s(&integrityProtection, QIODevice::WriteOnly); + s << qChecksum(ba.constData(), ba.length()); + } else if (m_protectionMode == ProtectionHash) { + flags |= CryptoFlagHash; + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + + integrityProtection += hash.result(); + } + + //prepend a random char to the string + char randomChar = char(qrand() & 0xFF); + ba = randomChar + integrityProtection + ba; + + int pos(0); + char lastChar(0); + + int cnt = ba.count(); + + while (pos < cnt) { + ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar; + lastChar = ba.at(pos); + ++pos; + } + + QByteArray resultArray; + resultArray.append(char(0x03)); //version for future updates to algorithm + resultArray.append(char(flags)); //encryption flags + resultArray.append(ba); + + m_lastError = ErrorNoError; + return resultArray; +} + +QString O0SimpleCrypt::encryptToString(const QString &plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + QByteArray cypher = encryptToByteArray(plaintextArray); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString O0SimpleCrypt::encryptToString(QByteArray plaintext) +{ + QByteArray cypher = encryptToByteArray(plaintext); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString O0SimpleCrypt::decryptToString(const QString &cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray plaintextArray = decryptToByteArray(cyphertextArray); + QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size()); + + return plaintext; +} + +QString O0SimpleCrypt::decryptToString(QByteArray cypher) +{ + QByteArray ba = decryptToByteArray(cypher); + QString plaintext = QString::fromUtf8(ba, ba.size()); + + return plaintext; +} + +QByteArray O0SimpleCrypt::decryptToByteArray(const QString &cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray ba = decryptToByteArray(cyphertextArray); + + return ba; +} + +QByteArray O0SimpleCrypt::decryptToByteArray(QByteArray cypher) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + if (!cypher.length()) { + m_lastError = ErrorUnknownVersion; + return QByteArray(); + } + + QByteArray ba = cypher; + + char version = ba.at(0); + + if (version != 3) { //we only work with version 3 + m_lastError = ErrorUnknownVersion; + qWarning() << "Invalid version or not a cyphertext."; + return QByteArray(); + } + + CryptoFlags flags = CryptoFlags(ba.at(1)); + + ba = ba.mid(2); + int pos(0); + int cnt(ba.count()); + char lastChar = 0; + + while (pos < cnt) { + char currentChar = ba[pos]; + ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8); + lastChar = currentChar; + ++pos; + } + + ba = ba.mid(1); //chop off the random number at the start + + bool integrityOk(true); + if (flags.testFlag(CryptoFlagChecksum)) { + if (ba.length() < 2) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + quint16 storedChecksum; + { + QDataStream s(&ba, QIODevice::ReadOnly); + s >> storedChecksum; + } + ba = ba.mid(2); + quint16 checksum = qChecksum(ba.constData(), ba.length()); + integrityOk = (checksum == storedChecksum); + } else if (flags.testFlag(CryptoFlagHash)) { + if (ba.length() < 20) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + QByteArray storedHash = ba.left(20); + ba = ba.mid(20); + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + integrityOk = (hash.result() == storedHash); + } + + if (!integrityOk) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + + if (flags.testFlag(CryptoFlagCompression)) { + ba = qUncompress(ba); + } + + m_lastError = ErrorNoError; + return ba; +} diff --git a/resources/tomboynotes/settings.kcfgc b/resources/tomboynotes/settings.kcfgc new file mode 100644 index 000000000..d373a17c2 --- /dev/null +++ b/resources/tomboynotes/settings.kcfgc @@ -0,0 +1,8 @@ +File=tomboynotesresource.kcfg +ClassName=Settings +Mutators=true +ItemAccessors=true +SetUserTexts=true +Singleton=true +#IncludeFiles= +GlobalEnums=true diff --git a/resources/tomboynotes/tomboycollectionsdownloadjob.cpp b/resources/tomboynotes/tomboycollectionsdownloadjob.cpp new file mode 100644 index 000000000..43756a9db --- /dev/null +++ b/resources/tomboynotes/tomboycollectionsdownloadjob.cpp @@ -0,0 +1,78 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboycollectionsdownloadjob.h" +#include "debug.h" +#include +#include +#include +#include + +TomboyCollectionsDownloadJob::TomboyCollectionsDownloadJob(const QString &collectionName, KIO::AccessManager *manager, QObject *parent) + : TomboyJobBase(manager, parent), + mCollectionName(collectionName) +{ +} + +Akonadi::Collection::List TomboyCollectionsDownloadJob::collections() const +{ + return mResultCollections; +} + +void TomboyCollectionsDownloadJob::start() +{ + // Get user informations + QNetworkRequest request(mContentURL); + mReply = mRequestor->get(request, QList()); + + connect(mReply, &QNetworkReply::finished, this, &TomboyCollectionsDownloadJob::onRequestFinished); + qCDebug(log_tomboynotesresource) << "TomboyCollectionsDownloadJob: Start network request"; +} + +void TomboyCollectionsDownloadJob::onRequestFinished() +{ + qCDebug(log_tomboynotesresource) << "TomboyCollectionsDownloadJob: Network request finished"; + checkReplyError(); + if (error() != TomboyJobError::NoError) { + setErrorText(mReply->errorString()); + emitResult(); + return; + } + + // Parse received data as JSON + const QJsonDocument document = QJsonDocument::fromJson(mReply->readAll(), Q_NULLPTR); + + const QJsonObject jo = document.object(); + qCDebug(log_tomboynotesresource) << "TomboyCollectionsDownloadJob: " << jo; + const QJsonValue collectionRevision = jo[QLatin1String("latest-sync-revision")]; + qCDebug(log_tomboynotesresource) << "TomboyCollectionsDownloadJob: " << collectionRevision; + + Akonadi::Collection c; + c.setParentCollection(Akonadi::Collection::root()); + c.setRemoteId(mContentURL); + c.setName(mCollectionName); + c.setRemoteRevision(QString::number(collectionRevision.toInt())); + qCDebug(log_tomboynotesresource) << "TomboyCollectionsDownloadJob: Sync revision " << collectionRevision.toString(); + + c.setContentMimeTypes({ Akonadi::NoteUtils::noteMimeType() }); + + mResultCollections << c; + + emitResult(); +} diff --git a/resources/tomboynotes/tomboycollectionsdownloadjob.h b/resources/tomboynotes/tomboycollectionsdownloadjob.h new file mode 100644 index 000000000..3b2bb8b9a --- /dev/null +++ b/resources/tomboynotes/tomboycollectionsdownloadjob.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYCOLLECTIONSDOWNLOADJOB_H +#define TOMBOYCOLLECTIONSDOWNLOADJOB_H + +#include "tomboyjobbase.h" +#include + +class TomboyCollectionsDownloadJob : public TomboyJobBase +{ + Q_OBJECT +public: + // ctor + explicit TomboyCollectionsDownloadJob(const QString &collectionName, KIO::AccessManager *manager, QObject *parent = Q_NULLPTR); + // returns the parsed results wrapped in Akonadi::Collection::List, see bellow + Akonadi::Collection::List collections() const; + + // automatically called by KJob + void start() Q_DECL_OVERRIDE; + +private Q_SLOTS: + // This will be called once the request is finished. + void onRequestFinished(); + +private: + Akonadi::Collection::List mResultCollections; + QString mCollectionName; +}; + +#endif // TOMBOYCOLLECTIONSDOWNLOADJOB_H diff --git a/resources/tomboynotes/tomboyitemdownloadjob.cpp b/resources/tomboynotes/tomboyitemdownloadjob.cpp new file mode 100644 index 000000000..0b40e7916 --- /dev/null +++ b/resources/tomboynotes/tomboyitemdownloadjob.cpp @@ -0,0 +1,89 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboyitemdownloadjob.h" +#include "debug.h" +#include +#include +#include +#include + +TomboyItemDownloadJob::TomboyItemDownloadJob(const Akonadi::Item &item, KIO::AccessManager *manager, QObject *parent) + : TomboyJobBase(manager, parent), + mResultItem(item) +{ +} + +Akonadi::Item TomboyItemDownloadJob::item() const +{ + return mResultItem; +} + +void TomboyItemDownloadJob::start() +{ + // Get the speicific note + mContentURL.chop(1); + QNetworkRequest request(mContentURL + QLatin1String("/") + mResultItem.remoteId()); + mReply = mRequestor->get(request, QList()); + + connect(mReply, &QNetworkReply::finished, this, &TomboyItemDownloadJob::onRequestFinished); + qCDebug(log_tomboynotesresource) << "TomboyItemDownloadJob: Start network request"; +} + +void TomboyItemDownloadJob::onRequestFinished() +{ + checkReplyError(); + if (error() != TomboyJobError::NoError) { + setErrorText(mReply->errorString()); + emitResult(); + return; + } + qCDebug(log_tomboynotesresource) << "TomboyItemDownloadJob: Network request finished. No error occured"; + + // Parse received data as JSON + const QJsonDocument document = QJsonDocument::fromJson(mReply->readAll(), Q_NULLPTR); + + const QJsonObject jsonNote = document.object(); + + qCDebug(log_tomboynotesresource) << "TomboyItemDownloadJob: JSON note: " << jsonNote; + + mResultItem.setRemoteRevision(QString::number(jsonNote[QLatin1String("last-sync-revision")].toInt())); + qCDebug(log_tomboynotesresource) << "TomboyItemDownloadJob: Sync revision " << mResultItem.remoteRevision(); + + // Set timestamp + const QString timeStampJson = jsonNote[QLatin1String("last-change-date")].toString(); + const QDateTime modificationTime = QDateTime::fromString(timeStampJson, Qt::ISODate); + mResultItem.setModificationTime(modificationTime); + + // Set note title + auto akonadiNote = KMime::Message::Ptr::create(); + akonadiNote->subject(true)->fromUnicodeString(jsonNote[QLatin1String("title")].toString(), "utf-8"); + + // Set note content + akonadiNote->contentType()->setMimeType("text/html"); + akonadiNote->contentType()->setCharset("utf-8"); + akonadiNote->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEquPr); + akonadiNote->mainBodyPart()->fromUnicodeString(jsonNote[QLatin1String("note-content")].toString()); + + // Add title and content to Akonadi::Item + akonadiNote->assemble(); + mResultItem.setPayload(akonadiNote); + + emitResult(); +} diff --git a/resources/tomboynotes/tomboyitemdownloadjob.h b/resources/tomboynotes/tomboyitemdownloadjob.h new file mode 100644 index 000000000..4390b3bb0 --- /dev/null +++ b/resources/tomboynotes/tomboyitemdownloadjob.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYITEMDOWNLOADJOB_H +#define TOMBOYITEMDOWNLOADJOB_H + +#include "tomboyjobbase.h" +#include + +class TomboyItemDownloadJob : public TomboyJobBase +{ + Q_OBJECT +public: + explicit TomboyItemDownloadJob(const Akonadi::Item &item, KIO::AccessManager *manager, QObject *parent = Q_NULLPTR); + // returns the parsed results wrapped in Akonadi::Item, see bellow + Akonadi::Item item() const; + + // automatically called by KJob + void start() Q_DECL_OVERRIDE; + +private Q_SLOTS: + // This will be called once the request is finished. + void onRequestFinished(); + +private: + Akonadi::Item mResultItem; +}; + +#endif // TOMBOYITEMDOWNLOADJOB_H diff --git a/resources/tomboynotes/tomboyitemsdownloadjob.cpp b/resources/tomboynotes/tomboyitemsdownloadjob.cpp new file mode 100644 index 000000000..4cef268fd --- /dev/null +++ b/resources/tomboynotes/tomboyitemsdownloadjob.cpp @@ -0,0 +1,73 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboyitemsdownloadjob.h" +#include "debug.h" +#include +#include +#include +#include + +TomboyItemsDownloadJob::TomboyItemsDownloadJob(const Akonadi::Collection::Id &id, KIO::AccessManager *manager, QObject *parent) + : TomboyJobBase(manager, parent), + mCollectionId(id) +{ +} + +Akonadi::Item::List TomboyItemsDownloadJob::items() const +{ + return mResultItems; +} + +void TomboyItemsDownloadJob::start() +{ + // Get all notes + QNetworkRequest request(mContentURL); + mReply = mRequestor->get(request, QList()); + + connect(mReply, &QNetworkReply::finished, this, &TomboyItemsDownloadJob::onRequestFinished); + qCDebug(log_tomboynotesresource) << "TomboyItemsDownloadJob: Start network request"; +} + +void TomboyItemsDownloadJob::onRequestFinished() +{ + qCDebug(log_tomboynotesresource) << "TomboyItemsDownloadJob: Network request finished"; + checkReplyError(); + if (error() != TomboyJobError::NoError) { + setErrorText(mReply->errorString()); + emitResult(); + return; + } + + // Parse received data as JSON + const QJsonDocument document = QJsonDocument::fromJson(mReply->readAll(), Q_NULLPTR); + + const QJsonObject jo = document.object(); + const QJsonArray notes = jo[QLatin1String("notes")].toArray(); + + Q_FOREACH (const auto ¬e, notes) { + Akonadi::Item item(Akonadi::NoteUtils::noteMimeType()); + item.setRemoteId(note.toObject()[QLatin1String("guid")].toString()); + mResultItems << item; + qCDebug(log_tomboynotesresource) << "TomboyItemsDownloadJob: Retriving note with id" << item.remoteId(); + } + + setError(TomboyJobError::NoError); + emitResult(); +} diff --git a/resources/tomboynotes/tomboyitemsdownloadjob.h b/resources/tomboynotes/tomboyitemsdownloadjob.h new file mode 100644 index 000000000..867a9e9ae --- /dev/null +++ b/resources/tomboynotes/tomboyitemsdownloadjob.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYITEMSDOWNLOADJOB_H +#define TOMBOYITEMSDOWNLOADJOB_H + +#include "tomboyjobbase.h" +#include + +class TomboyItemsDownloadJob : public TomboyJobBase +{ + Q_OBJECT +public: + // ctor + explicit TomboyItemsDownloadJob(const Akonadi::Collection::Id &id, KIO::AccessManager *manager, QObject *parent = Q_NULLPTR); + // returns the parsed results wrapped in Akonadi::Item::List, see bellow + Akonadi::Item::List items() const; + + // automatically called by KJob + void start() Q_DECL_OVERRIDE; + +private Q_SLOTS: + // This will be called once the request is finished. + void onRequestFinished(); + +private: + Akonadi::Collection::Id mCollectionId; + Akonadi::Item::List mResultItems; +}; + +#endif // TOMBOYITEMSDOWNLOADJOB_H diff --git a/resources/tomboynotes/tomboyitemuploadjob.cpp b/resources/tomboynotes/tomboyitemuploadjob.cpp new file mode 100644 index 000000000..7d29eb4eb --- /dev/null +++ b/resources/tomboynotes/tomboyitemuploadjob.cpp @@ -0,0 +1,143 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboyitemuploadjob.h" +#include "debug.h" +#include +#include +#include +#include +#include + +TomboyItemUploadJob::TomboyItemUploadJob(const Akonadi::Item &item, JobType jobType, KIO::AccessManager *manager, QObject *parent) + : TomboyJobBase(manager, parent), + mSourceItem(item), + mJobType(jobType) +{ + mSourceItem = Akonadi::Item(item); + if (item.hasPayload()) { + mNoteContent = item.payload(); + } + + mRemoteRevision = item.parentCollection().remoteRevision().toInt(); + + // Create random remote id if adding new item + if (jobType == JobType::AddItem) { + mSourceItem.setRemoteId(QUuid::createUuid().toString()); + } +} + +Akonadi::Item TomboyItemUploadJob::item() const +{ + return mSourceItem; +} + +JobType TomboyItemUploadJob::jobType() const +{ + return mJobType; +} + +void TomboyItemUploadJob::start() +{ + // Convert note to JSON + QJsonObject jsonNote; + jsonNote[QLatin1String("guid")] = mSourceItem.remoteId(); + switch (mJobType) { + case JobType::DeleteItem: + jsonNote[QLatin1String("command")] = QStringLiteral("delete"); + break; + case JobType::AddItem: + jsonNote[QLatin1String("create-date")] = getCurrentISOTime(); + // Missing break is intended + case JobType::ModifyItem: + jsonNote[QLatin1String("title")] = mNoteContent->headerByType("subject")->asUnicodeString(); + jsonNote[QLatin1String("note-content")] = mNoteContent->mainBodyPart()->decodedText(); + jsonNote[QLatin1String("note-content-version")] = QStringLiteral("1"); + jsonNote[QLatin1String("last-change-date")] = getCurrentISOTime(); + } + + // Create the full JSON object + QJsonArray noteChanges; + noteChanges.append(jsonNote); + QJsonObject postJson; + postJson[QLatin1String("note-changes")] = noteChanges; + postJson[QLatin1String("latest-sync-revision")] = QString::number(++mRemoteRevision); + QJsonDocument postData; + postData.setObject(postJson); + + // Network request + QNetworkRequest request(mContentURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json; boundary=7d44e178b0439")); + request.setHeader(QNetworkRequest::ContentLengthHeader, postData.toJson().length()); + mReply = mRequestor->put(request, QList(), postData.toJson()); + connect(mReply, &QNetworkReply::finished, this, &TomboyItemUploadJob::onRequestFinished); + qCDebug(log_tomboynotesresource) << "TomboyItemUploadJob: Start network request"; +} + +void TomboyItemUploadJob::onRequestFinished() +{ + checkReplyError(); + if (error() != TomboyJobError::NoError) { + setErrorText(mReply->errorString()); + emitResult(); + return; + } + qCDebug(log_tomboynotesresource) << "TomboyItemUploadJob: Network request finished. No error occured"; + + // Parse received data as JSON + const QJsonDocument document = QJsonDocument::fromJson(mReply->readAll(), Q_NULLPTR); + + const QJsonObject jo = document.object(); + const QJsonArray notes = jo[QLatin1String("notes")].toArray(); + + // Check if server status is as expected + bool found = false; + Q_FOREACH (const auto note, notes) { + found = (note.toObject()[QLatin1String("guid")].toString() == mSourceItem.remoteId()); + if (found) { + break; + } + } + if (mJobType == JobType::DeleteItem && found) { + setError(TomboyJobError::PermanentError); + setErrorText(i18n("Sync error. Server status not as expected!")); + emitResult(); + return; + } + if (mJobType != JobType::DeleteItem && !found) { + setError(TomboyJobError::PermanentError); + setErrorText(i18n("Sync error. Server status not as expected!")); + emitResult(); + return; + } + + setError(TomboyJobError::NoError); + emitResult(); +} + +QString TomboyItemUploadJob::getCurrentISOTime() const +{ + QDateTime local = QDateTime::currentDateTime(); + QDateTime utc = local.toUTC(); + utc.setTimeSpec(Qt::LocalTime); + int utcOffset = utc.secsTo(local); + local.setUtcOffset(utcOffset); + + return local.toString(Qt::ISODate); +} diff --git a/resources/tomboynotes/tomboyitemuploadjob.h b/resources/tomboynotes/tomboyitemuploadjob.h new file mode 100644 index 000000000..28ad840da --- /dev/null +++ b/resources/tomboynotes/tomboyitemuploadjob.h @@ -0,0 +1,63 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYITEMUPLOADJOB_H +#define TOMBOYITEMUPLOADJOB_H + +#include "tomboyjobbase.h" +#include +#include + +enum class JobType { + AddItem, + ModifyItem, + DeleteItem +}; + +class TomboyItemUploadJob : public TomboyJobBase +{ + Q_OBJECT +public: + TomboyItemUploadJob(const Akonadi::Item &item, JobType jobType, KIO::AccessManager *manager, QObject *parent = Q_NULLPTR); + + // Returns mSourceItem for post-processing purposes + Akonadi::Item item() const; + + JobType jobType() const; + + // automatically called by KJob + void start() Q_DECL_OVERRIDE; + +private Q_SLOTS: + // This will be called once the request is finished. + void onRequestFinished(); + +private: + // Workaround for https://bugreports.qt-project.org/browse/QTBUG-26161 Qt bug + QString getCurrentISOTime() const; + + Akonadi::Item mSourceItem; + KMime::Message::Ptr mNoteContent; + + JobType mJobType; + + int mRemoteRevision; +}; + +#endif // TOMBOYITEMUPLOADJOB_H diff --git a/resources/tomboynotes/tomboyjobbase.cpp b/resources/tomboynotes/tomboyjobbase.cpp new file mode 100644 index 000000000..7a8b80413 --- /dev/null +++ b/resources/tomboynotes/tomboyjobbase.cpp @@ -0,0 +1,59 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboyjobbase.h" + +TomboyJobBase::TomboyJobBase(KIO::AccessManager *manager, QObject *parent) + : KCompositeJob(parent), + mO1(new O1Tomboy(this)), + mManager(manager), + mReply(Q_NULLPTR) + +{ + mRequestor = new O1Requestor(mManager, mO1, this); +} + +void TomboyJobBase::setServerURL(const QString &apiurl, const QString &contenturl) +{ + mO1->setBaseURL(apiurl); + mApiURL = apiurl + QStringLiteral("/api/1.0"); + mContentURL = contenturl; +} + +void TomboyJobBase::setAuthentication(const QString &token, const QString &secret) +{ + mO1->restoreAuthData(token, secret); +} + +void TomboyJobBase::checkReplyError() +{ + switch (mReply->error()) { + case QNetworkReply::NoError : + setError(TomboyJobError::NoError); + break; + case QNetworkReply::RemoteHostClosedError: + case QNetworkReply::HostNotFoundError: + case QNetworkReply::TimeoutError: + setError(TomboyJobError::TemporaryError); + break; + default: + setError(TomboyJobError::PermanentError); + break; + } +} diff --git a/resources/tomboynotes/tomboyjobbase.h b/resources/tomboynotes/tomboyjobbase.h new file mode 100644 index 000000000..e833a8a70 --- /dev/null +++ b/resources/tomboynotes/tomboyjobbase.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYJOBBASE_H +#define TOMBOYJOBBASE_H + +#include "o1tomboy.h" +#include "o2/o1requestor.h" +#include +#include +#include + +enum TomboyJobError { + NoError, + TemporaryError, + PermanentError +}; + +class TomboyJobBase : public KCompositeJob +{ + Q_OBJECT +public: + explicit TomboyJobBase(KIO::AccessManager *manager, QObject *parent = Q_NULLPTR); + + void setServerURL(const QString &apiurl, const QString &contenturl); + void setAuthentication(const QString &token, const QString &secret); + +protected: + KIO::Integration::AccessManager *mManager; + O1Requestor *mRequestor; + O1Tomboy *mO1; + QNetworkReply *mReply; + + QString mApiURL; + QString mContentURL; + + void checkReplyError(); +}; + +#endif // TOMBOYJOBBASE_H diff --git a/resources/tomboynotes/tomboynotesresource.cpp b/resources/tomboynotes/tomboynotesresource.cpp new file mode 100644 index 000000000..2457d2f3b --- /dev/null +++ b/resources/tomboynotes/tomboynotesresource.cpp @@ -0,0 +1,328 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboynotesresource.h" +#include "debug.h" +#include "configdialog.h" +#include "settings.h" +#include "settingsadaptor.h" +#include "tomboyserverauthenticatejob.h" +#include "tomboycollectionsdownloadjob.h" +#include "tomboyitemdownloadjob.h" +#include "tomboyitemsdownloadjob.h" +#include "tomboyitemuploadjob.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace Akonadi; + +TomboyNotesResource::TomboyNotesResource(const QString &id) + : ResourceBase(id) +{ + new SettingsAdaptor(Settings::self()); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), + Settings::self(), + QDBusConnection::ExportAdaptors); + + // Akonadi:Item should always provide the payload + changeRecorder()->itemFetchScope().fetchFullPayload(true); + + // Status message stuff + mStatusMessageTimer = new QTimer(this); + mStatusMessageTimer->setSingleShot(true); + connect(mStatusMessageTimer, &QTimer::timeout, [ = ]() { + Q_EMIT status(Akonadi::AgentBase::Idle, QString()); + }); + connect(this, &AgentBase::error, this, &TomboyNotesResource::showError); + + mUploadJobProcessRunning = false; + + mManager = new KIO::AccessManager(this); + connect(mManager, &KIO::AccessManager::sslErrors, this, &TomboyNotesResource::onSslError); + + qCDebug(log_tomboynotesresource) << "Resource started"; +} + +TomboyNotesResource::~TomboyNotesResource() +{ +} + +void TomboyNotesResource::retrieveCollections() +{ + qCDebug(log_tomboynotesresource) << "Retrieving collections started"; + + if (configurationNotValid()) { + cancelTask(i18n("Resource configuration is not valid")); + return; + } + + auto job = new TomboyCollectionsDownloadJob(Settings::collectionName(), mManager, this); + job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret()); + job->setServerURL(Settings::serverURL(), Settings::userURL()); + // connect to its result() signal + connect(job, &KJob::result, this, &TomboyNotesResource::onCollectionsRetrieved); + job->start(); + qCDebug(log_tomboynotesresource) << "Retriving collections job started"; +} + +void TomboyNotesResource::retrieveItems(const Akonadi::Collection &collection) +{ + if (configurationNotValid()) { + cancelTask(i18n("Resource configuration is not valid")); + return; + } + + // create the job + auto job = new TomboyItemsDownloadJob(collection.id(), mManager, this); + job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret()); + job->setServerURL(Settings::serverURL(), Settings::contentURL()); + // connect to its result() signal + connect(job, &KJob::result, this, &TomboyNotesResource::onItemsRetrieved); + job->start(); + qCDebug(log_tomboynotesresource) << "Retriving items job started"; +} + +bool TomboyNotesResource::retrieveItem(const Akonadi::Item &item, const QSet &parts) +{ + Q_UNUSED(parts); + + if (configurationNotValid()) { + cancelTask(i18n("Resource configuration is not valid")); + return false; + } + + // this method is called when Akonadi wants more data for a given item. + auto job = new TomboyItemDownloadJob(item, mManager, this); + job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret()); + job->setServerURL(Settings::serverURL(), Settings::contentURL()); + // connect to its result() signal + connect(job, &KJob::result, this, &TomboyNotesResource::onItemRetrieved); + job->start(); + qCDebug(log_tomboynotesresource) << "Retriving item data job started"; + + return true; +} + +void TomboyNotesResource::onAuthorizationFinished(KJob *kjob) +{ + // Saves the received client authentication data in the settings + qCDebug(log_tomboynotesresource) << "Authorization job finished"; + auto job = qobject_cast(kjob); + if (job->error() == TomboyJobError::NoError) { + Settings::setRequestToken(job->getRequestToken()); + Settings::setRequestTokenSecret(job->getRequestTokenSecret()); + Settings::setContentURL(job->getContentUrl()); + Settings::setUserURL(job->getUserURL()); + Settings::self()->save(); + synchronizeCollectionTree(); + synchronize(); + } else { + showError(job->errorText()); + } +} + +void TomboyNotesResource::onCollectionsRetrieved(KJob *kjob) +{ + auto job = qobject_cast(kjob); + if (job->error() != TomboyJobError::NoError) { + cancelTask(); + showError(job->errorText()); + return; + } + + collectionsRetrieved(job->collections()); +} + +void TomboyNotesResource::onItemChangeCommitted(KJob *kjob) +{ + auto job = qobject_cast(kjob); + mUploadJobProcessRunning = false; + switch (job->error()) { + case TomboyJobError::PermanentError: + cancelTask(); + showError(job->errorText()); + return; + case TomboyJobError::TemporaryError: + retryAfterFailure(job->errorString()); + return; + case TomboyJobError::NoError: + changeCommitted(job->item()); + // The data should be actualized for the next UploadJob + synchronize(); + return; + } +} + +void TomboyNotesResource::onItemRetrieved(KJob *kjob) +{ + auto job = qobject_cast(kjob); + + if (job->error() != TomboyJobError::NoError) { + cancelTask(); + showError(job->errorText()); + return; + } + + itemRetrieved(job->item()); + qCDebug(log_tomboynotesresource) << "Retriving item data job with remoteId " << job->item().remoteId() << " finished"; +} + +void TomboyNotesResource::onItemsRetrieved(KJob *kjob) +{ + auto job = qobject_cast(kjob); + if (job->error() != TomboyJobError::NoError) { + cancelTask(); + showError(job->errorText()); + return; + } + + itemsRetrieved(job->items()); + qCDebug(log_tomboynotesresource) << "Retriving items job finished"; +} + +void TomboyNotesResource::onSslError(QNetworkReply *reply, const QList &errors) +{ + if (Settings::ignoreSslErrors()) { + reply->ignoreSslErrors(); + } +} + +void TomboyNotesResource::aboutToQuit() +{ + // TODO: any cleanup you need to do while there is still an active + // event loop. The resource will terminate after this method returns +} + +void TomboyNotesResource::configure(WId windowId) +{ + qCDebug(log_tomboynotesresource) << "Resource configuration started"; + + ConfigDialog dialog(Settings::self()); + + if (windowId) { + KWindowSystem::setMainWindow(&dialog, windowId); + } + + // Run the configuration dialog an save settings if accepted + if (dialog.exec() != QDialog::Accepted) { + return; + } + + dialog.saveSettings(); + setAgentName(Settings::collectionName()); + + if (configurationNotValid()) { + auto job = new TomboyServerAuthenticateJob(mManager, this); + job->setServerURL(Settings::serverURL(), QString()); + connect(job, &KJob::result, this, &TomboyNotesResource::onAuthorizationFinished); + job->start(); + qCDebug(log_tomboynotesresource) << "Authorization job started"; + } else { + synchronize(); + } +} + +void TomboyNotesResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) +{ + Q_UNUSED(collection); + if (Settings::readOnly() || configurationNotValid()) { + cancelTask(i18n("Resource is read-only")); + return; + } + + if (mUploadJobProcessRunning) { + retryAfterFailure(QString()); + return; + } + + auto job = new TomboyItemUploadJob(item, JobType::AddItem, mManager, this); + job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret()); + job->setServerURL(Settings::serverURL(), Settings::contentURL()); + connect(job, &KJob::result, this, &TomboyNotesResource::onItemChangeCommitted); + mUploadJobProcessRunning = true; + job->start(); +} + +void TomboyNotesResource::itemChanged(const Akonadi::Item &item, const QSet &parts) +{ + Q_UNUSED(parts); + if (Settings::readOnly() || configurationNotValid()) { + cancelTask(i18n("Resource is read-only")); + return; + } + + if (mUploadJobProcessRunning) { + retryAfterFailure(QString()); + return; + } + + auto job = new TomboyItemUploadJob(item, JobType::ModifyItem, mManager, this); + job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret()); + job->setServerURL(Settings::serverURL(), Settings::contentURL()); + connect(job, &KJob::result, this, &TomboyNotesResource::onItemChangeCommitted); + mUploadJobProcessRunning = true; + job->start(); +} + +void TomboyNotesResource::itemRemoved(const Akonadi::Item &item) +{ + if (Settings::readOnly() || configurationNotValid()) { + cancelTask(i18n("Resource is read-only")); + return; + } + + if (mUploadJobProcessRunning) { + retryAfterFailure(QString()); + return; + } + + auto job = new TomboyItemUploadJob(item, JobType::DeleteItem, mManager, this); + job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret()); + job->setServerURL(Settings::serverURL(), Settings::contentURL()); + connect(job, &KJob::result, this, &TomboyNotesResource::onItemChangeCommitted); + mUploadJobProcessRunning = true; + job->start(); +} + +bool TomboyNotesResource::configurationNotValid() const +{ + return Settings::requestToken().isEmpty() || Settings::requestToken().isEmpty() || Settings::contentURL().isEmpty(); +} + +void TomboyNotesResource::retryAfterFailure(const QString &errorMessage) +{ + Q_EMIT status(Broken, errorMessage); + deferTask(); + setTemporaryOffline(Settings::self()->refreshInterval() <= 0 ? 300 : Settings::self()->refreshInterval() * 60); +} + +void TomboyNotesResource::showError(const QString &errorText) +{ + Q_EMIT status(Akonadi::AgentBase::Idle, errorText); + mStatusMessageTimer->start(1000 * 10); +} + +AKONADI_RESOURCE_MAIN(TomboyNotesResource) + +#include "moc_tomboynotesresource.cpp" diff --git a/resources/tomboynotes/tomboynotesresource.desktop b/resources/tomboynotes/tomboynotesresource.desktop new file mode 100644 index 000000000..2317f011a --- /dev/null +++ b/resources/tomboynotes/tomboynotesresource.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Akonadi TomboyNotes Resource +Comment=Resource for Tomboy compatible notes server +Type=AkonadiResource +Exec=akonadi_tomboynotes_resource +Icon=view-pim-notes + +X-Akonadi-MimeTypes=text/x-vnd.akonadi.note +X-Akonadi-Capabilities=Resource,Notes +X-Akonadi-Identifier=akonadi_tomboynotes_resource diff --git a/resources/tomboynotes/tomboynotesresource.h b/resources/tomboynotes/tomboynotesresource.h new file mode 100644 index 000000000..c78cb8ff2 --- /dev/null +++ b/resources/tomboynotes/tomboynotesresource.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYNOTESRESOURCE_H +#define TOMBOYNOTESRESOURCE_H + +#include +#include + +class TomboyNotesResource : public Akonadi::ResourceBase, + public Akonadi::AgentBase::Observer +{ + Q_OBJECT + +public: + TomboyNotesResource(const QString &id); + ~TomboyNotesResource(); + +public Q_SLOTS: + void configure(WId windowId) Q_DECL_OVERRIDE; + +protected Q_SLOTS: + // Standard akonadi slots + void retrieveCollections() Q_DECL_OVERRIDE; + void retrieveItems(const Akonadi::Collection &col) Q_DECL_OVERRIDE; + bool retrieveItem(const Akonadi::Item &item, const QSet &parts) Q_DECL_OVERRIDE; + + // Slots for Job result handling + void onAuthorizationFinished(KJob *kjob); + void onCollectionsRetrieved(KJob *kjob); + void onItemChangeCommitted(KJob *kjob); + void onItemRetrieved(KJob *kjob); + void onItemsRetrieved(KJob *kjob); + +private Q_SLOTS: + // SSL error handling + void onSslError(QNetworkReply *reply, const QList &errors); + +protected: + void aboutToQuit() Q_DECL_OVERRIDE; + + // Standard akonadi + void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) Q_DECL_OVERRIDE; + void itemChanged(const Akonadi::Item &item, const QSet &parts) Q_DECL_OVERRIDE; + void itemRemoved(const Akonadi::Item &item) Q_DECL_OVERRIDE; + +private: + bool configurationNotValid() const; + + void retryAfterFailure(const QString &errorMessage); + // Status handling + void showError(const QString &errorText); + QTimer *mStatusMessageTimer; + + // Only one UploadJob should run per time + bool mUploadJobProcessRunning; + + KIO::AccessManager *mManager; +}; + +#endif diff --git a/resources/tomboynotes/tomboynotesresource.kcfg b/resources/tomboynotes/tomboynotesresource.kcfg new file mode 100644 index 000000000..eb1a08192 --- /dev/null +++ b/resources/tomboynotes/tomboynotesresource.kcfg @@ -0,0 +1,50 @@ + + + + + + + + + + + http://localhost/owncloud/index.php/apps/grauphel + + + + + + + + + + + + + + + + + + + + + + + + false + + + + false + + + + 5 + + + diff --git a/resources/tomboynotes/tomboyserverauthenticatejob.cpp b/resources/tomboynotes/tomboyserverauthenticatejob.cpp new file mode 100644 index 000000000..e0ddae0e2 --- /dev/null +++ b/resources/tomboynotes/tomboyserverauthenticatejob.cpp @@ -0,0 +1,127 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "tomboyserverauthenticatejob.h" +#include "debug.h" +#include +#include +#include +#include + +TomboyServerAuthenticateJob::TomboyServerAuthenticateJob(KIO::AccessManager *manager, QObject *parent) + : TomboyJobBase(manager, parent), + mWebView(new QWebEngineView(Q_NULLPTR)) +{ + // Connect the o2 authenfication signals + connect(mO1, &O1::linkingFailed, this, &TomboyServerAuthenticateJob::onLinkingFailed); + connect(mO1, &O1::linkingSucceeded, this, &TomboyServerAuthenticateJob::onLinkingSucceeded); + connect(mO1, &O1::openBrowser, this, &TomboyServerAuthenticateJob::onOpenBrowser); + connect(mO1, &O1::closeBrowser, mWebView, &QWebEngineView::close); +} + +TomboyServerAuthenticateJob::~TomboyServerAuthenticateJob() +{ + delete mWebView; +} + +void TomboyServerAuthenticateJob::start() +{ + mO1->link(); +} + +QString TomboyServerAuthenticateJob::getRequestToken() const +{ + return mO1->getRequestToken(); +} + +QString TomboyServerAuthenticateJob::getRequestTokenSecret() const +{ + return mO1->getRequestTokenSecret(); +} + +QString TomboyServerAuthenticateJob::getContentUrl() const +{ + return mContentURL; +} + +QString TomboyServerAuthenticateJob::getUserURL() const +{ + return mUserURL; +} + +void TomboyServerAuthenticateJob::onLinkingFailed() +{ + setError(TomboyJobError::PermanentError); + setErrorText(i18n("Authorization has failed! It could be an SSL error!")); + emitResult(); +} + +void TomboyServerAuthenticateJob::onLinkingSucceeded() +{ + QNetworkRequest request(mApiURL); + mReply = mRequestor->get(request, QList()); + + connect(mReply, &QNetworkReply::finished, this, &TomboyServerAuthenticateJob::onApiRequestFinished); + qCDebug(log_tomboynotesresource) << "TomboyServerAuthenticateJob: Start network request"; +} + +void TomboyServerAuthenticateJob::onOpenBrowser(const QUrl &url) +{ + mWebView->setUrl(url); + mWebView->show(); +} + +void TomboyServerAuthenticateJob::onApiRequestFinished() +{ + checkReplyError(); + if (error() != TomboyJobError::NoError) { + setErrorText(mReply->errorString()); + emitResult(); + return; + } + + // Parse received data as JSON and get user-href + const QJsonDocument document = QJsonDocument::fromJson(mReply->readAll(), Q_NULLPTR); + const QJsonObject jo = document.object(); + mUserURL = jo[QLatin1String("user-ref")].toObject()[QLatin1String("api-ref")].toString(); + + QNetworkRequest request(mUserURL); + mReply = mRequestor->get(request, QList()); + + connect(mReply, &QNetworkReply::finished, this, &TomboyServerAuthenticateJob::onUserRequestFinished); + qCDebug(log_tomboynotesresource) << "TomboyServerAuthenticateJob: Start network request"; +} + +void TomboyServerAuthenticateJob::onUserRequestFinished() +{ + checkReplyError(); + if (error() != TomboyJobError::NoError) { + setErrorText(mReply->errorString()); + emitResult(); + return; + } + + // Parse received data as JSON and get contentURL + QJsonDocument document = QJsonDocument::fromJson(mReply->readAll(), Q_NULLPTR); + QJsonObject jo = document.object(); + mContentURL = jo[QLatin1String("notes-ref")].toObject()[QLatin1String("api-ref")].toString(); + + setError(TomboyJobError::NoError); + emitResult(); +} diff --git a/resources/tomboynotes/tomboyserverauthenticatejob.h b/resources/tomboynotes/tomboyserverauthenticatejob.h new file mode 100644 index 000000000..0eef7a04a --- /dev/null +++ b/resources/tomboynotes/tomboyserverauthenticatejob.h @@ -0,0 +1,57 @@ +/* + Copyright (c) 2016 Stefan Stäglich + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef TOMBOYSERVERAUTHENTICATEJOB_H +#define TOMBOYSERVERAUTHENTICATEJOB_H + +#include "tomboyjobbase.h" +#include +#include + +class TomboyServerAuthenticateJob : public TomboyJobBase +{ + Q_OBJECT +public: + TomboyServerAuthenticateJob(KIO::AccessManager *manager, QObject *parent = Q_NULLPTR); + + ~TomboyServerAuthenticateJob(); + + QString getRequestToken() const; + QString getRequestTokenSecret() const; + QString getContentUrl() const; + QString getUserURL() const; + + void start() Q_DECL_OVERRIDE; + +private Q_SLOTS: + void onLinkingFailed(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + + void onApiRequestFinished(); + void onUserRequestFinished(); + +private: + QString mUserURL; + + QWebEngineView *mWebView; + +}; + +#endif // TOMBOYSERVERAUTHENTICATEJOB_H