diff --git a/core/libs/dplugins/webservices/CMakeLists.txt b/core/libs/dplugins/webservices/CMakeLists.txt index 02e5780cdd..bc0c30e254 100644 --- a/core/libs/dplugins/webservices/CMakeLists.txt +++ b/core/libs/dplugins/webservices/CMakeLists.txt @@ -1,62 +1,64 @@ # # Copyright (c) 2010-2020 by Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. APPLY_COMMON_POLICIES() # OAuth2 library --------------------------------------------------- # The o2 library does not adhere to the flags we use remove_definitions( -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY) # Remove unwanted warnings from O2 library REMOVE_GCC_COMPILER_WARNINGS("-pedantic") DISABLE_CLANG_COMPILER_WARNINGS("4.99.99" "-Wno-extra-semi") # It also does not export symbols, so export all by default unset(CMAKE_CXX_VISIBILITY_PRESET) if(WIN32) add_definitions(-DO2_DLL_EXPORT) endif() set(libwso2_SRCS o2/src/o2.cpp o2/src/o2reply.cpp o2/src/o2replyserver.cpp o2/src/o2requestor.cpp o2/src/o2simplecrypt.cpp o2/src/o0settingsstore.cpp o2/src/o0baseauth.cpp o2/src/o0abstractstore.h o2/src/o0globals.h # Enable when needed o2/src/o1.cpp o2/src/o1requestor.cpp o2/src/o1timedreply.cpp o2/src/o1smugmug.cpp o2/src/o1twitter.h o2/src/oxtwitter.cpp #o2/src/o1dropbox.h #o2/src/o1flickr.h #o2/src/o2gft.cpp o2/src/o2facebook.cpp #o2/src/o2skydrive.cpp #o2/src/o2hubic.cpp #o2/src/o2uber.cpp + #o2/src/o2msgraph.cpp + #o2/src/o2vimeo.cpp ) # Used by digikamcore add_library(core_libwso2_obj OBJECT ${libwso2_SRCS}) target_compile_definitions(core_libwso2_obj PRIVATE digikamcore_EXPORTS ) diff --git a/core/libs/dplugins/webservices/o2/examples/CMakeLists.txt b/core/libs/dplugins/webservices/o2/examples/CMakeLists.txt index 8dcfda0684..8582ef88bb 100644 --- a/core/libs/dplugins/webservices/o2/examples/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/examples/CMakeLists.txt @@ -1,13 +1,26 @@ cmake_minimum_required(VERSION 2.8.11) if(o2_WITH_FACEBOOK) add_subdirectory(facebookdemo) endif(o2_WITH_FACEBOOK) +if(o2_WITH_GOOGLE) + add_subdirectory(youtubedemo) +endif(o2_WITH_GOOGLE) + if(o2_WITH_TWITTER) add_subdirectory(twitterdemo) endif(o2_WITH_TWITTER) -if(o2_WITH_SPOTIFY) - add_subdirectory(spotifydemo) -endif(o2_WITH_SPOTIFY) +#if(o2_WITH_SPOTIFY) +# add_subdirectory(spotifydemo) +#endif(o2_WITH_SPOTIFY) + +if (o2_WITH_VIMEO) + add_subdirectory(vimeodemo) +endif(o2_WITH_VIMEO) + +if (o2_WITH_MSGRAPH) + add_subdirectory(msgraphdemo) + add_subdirectory(msgraphexternalinterceptordemo) +endif(o2_WITH_MSGRAPH) diff --git a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt b/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt index afc3d27060..3326740123 100644 --- a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt @@ -1,43 +1,44 @@ cmake_minimum_required(VERSION 2.8.11) project( fbexample ) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") if(o2_WITH_QT5) # Qt5 packages find their own dependencies. find_package(Qt5Core REQUIRED) find_package(Qt5Widgets REQUIRED) find_package(Qt5Script REQUIRED) find_package(Qt5Network REQUIRED) else(o2_WITH_QT5) set(QT_USE_QTNETWORK true) set(QT_USE_QTSCRIPT true) find_package(Qt4 REQUIRED) endif(o2_WITH_QT5) if (NOT o2_WITH_QT5) include( ${QT_USE_FILE} ) endif(NOT o2_WITH_QT5) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) set(fb_SRCS main.cpp fbdemo.cpp + fbdemo.h ) if(NOT o2_WITH_QT5) add_definitions(${QT4_DEFINITIONS}) endif(NOT o2_WITH_QT5) add_executable( fbexample ${fb_SRCS} ) if(o2_WITH_QT5) qt5_use_modules( fbexample Core Widgets Network ) target_link_libraries( fbexample o2 ) else(o2_WITH_QT5) target_link_libraries( fbexample ${QT_LIBRARIES} o2 ) endif(o2_WITH_QT5) diff --git a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/CMakeLists.txt similarity index 68% copy from core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt copy to core/libs/dplugins/webservices/o2/examples/msgraphdemo/CMakeLists.txt index afc3d27060..449a1368dd 100644 --- a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/CMakeLists.txt @@ -1,43 +1,44 @@ cmake_minimum_required(VERSION 2.8.11) -project( fbexample ) +project( msgraphexample ) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") if(o2_WITH_QT5) # Qt5 packages find their own dependencies. find_package(Qt5Core REQUIRED) - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Script REQUIRED) + find_package(Qt5Gui REQUIRED) find_package(Qt5Network REQUIRED) + find_package(Qt5Widgets REQUIRED) else(o2_WITH_QT5) set(QT_USE_QTNETWORK true) set(QT_USE_QTSCRIPT true) find_package(Qt4 REQUIRED) endif(o2_WITH_QT5) if (NOT o2_WITH_QT5) include( ${QT_USE_FILE} ) endif(NOT o2_WITH_QT5) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) -set(fb_SRCS +set(msgraph_SRCS main.cpp - fbdemo.cpp + msgraphdemo.cpp + msgraphdemo.h ) if(NOT o2_WITH_QT5) add_definitions(${QT4_DEFINITIONS}) endif(NOT o2_WITH_QT5) -add_executable( fbexample ${fb_SRCS} ) +add_executable( msgraphexample ${msgraph_SRCS} ) if(o2_WITH_QT5) - qt5_use_modules( fbexample Core Widgets Network ) - target_link_libraries( fbexample o2 ) + qt5_use_modules( msgraphexample Core Gui Network Widgets ) + target_link_libraries( msgraphexample o2 ) else(o2_WITH_QT5) - target_link_libraries( fbexample ${QT_LIBRARIES} o2 ) + target_link_libraries( msgraphexample ${QT_LIBRARIES} o2 ) endif(o2_WITH_QT5) diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphdemo/main.cpp b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/main.cpp new file mode 100644 index 0000000000..ec486b4867 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/main.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include +#include + +#include "msgraphdemo.h" + +class Helper : public QObject { + Q_OBJECT + +public: + Helper() : QObject(), demo_(this) {} + +public slots: + void run() { + connect(&demo_, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(&demo_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(&demo_, SIGNAL(userPrincipalNameReceived()), this, SLOT(onUserPrincipalNameReceived())); + connect(&demo_, SIGNAL(userPrincipalNameFailed()), this, SLOT(onUserPrincipalNameFailed())); + + demo_.doOAuth(O2::GrantFlowAuthorizationCode); + } + + void onLinkingFailed() { + qDebug() << "Linking failed!"; + qApp->exit(1); + } + + void onLinkingSucceeded() { + qDebug() << "Linking succeeded!"; + demo_.getUserPrincipalName(); + } + + void onUserPrincipalNameFailed() { + qDebug() << "Error getting userPrincipalName!"; + qApp->exit(1); + } + + void onUserPrincipalNameReceived() { + qDebug() << "UserPrincipalName received!"; + qApp->quit(); + } + +private: + MsgraphDemo demo_; +}; + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QCoreApplication::setOrganizationName("O2"); + QCoreApplication::setApplicationName("Msgraph Example"); + Helper helper; + QTimer::singleShot(0, &helper, SLOT(run())); + return a.exec(); +} + +#include "main.moc" diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.cpp b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.cpp new file mode 100644 index 0000000000..907f39423e --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "msgraphdemo.h" +#include "o0globals.h" +#include "o0settingsstore.h" +#include "o2requestor.h" + +const char MSGRAPH_APP_ID[] = "YOUR_MSGRAPH_APP_ID"; +const char MSGRAPH_APP_SECRET[] = "YOUR_MSGRAPH_APP_SECRET"; +const char MSGRAPH_SCOPE[] = "User.Read"; +const char MSGRAPH_USER_INFO_URL[] = "https://graph.microsoft.com/v1.0/me"; + +const int localPort = 8888; + +#define QENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) +#define GRANTFLOW_STR(v) QString(QENUM_NAME(O2, GrantFlow, v)) + +MsgraphDemo::MsgraphDemo(QObject *parent) : + QObject(parent), requestId_(0) { + o2Msgraph_ = new O2Msgraph(this); + + o2Msgraph_->setClientId(MSGRAPH_APP_ID); + o2Msgraph_->setClientSecret(MSGRAPH_APP_SECRET); + o2Msgraph_->setLocalPort(localPort); + o2Msgraph_->setScope(MSGRAPH_SCOPE); + + // Create a store object for writing the received tokens + O0SettingsStore *store = new O0SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("msgraph"); + o2Msgraph_->setStore(store); + + connect(o2Msgraph_, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o2Msgraph_, SIGNAL(linkingFailed()), this, SIGNAL(linkingFailed())); + connect(o2Msgraph_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o2Msgraph_, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o2Msgraph_, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); +} + +void MsgraphDemo::doOAuth(O2::GrantFlow grantFlowType) { + qDebug() << "Starting OAuth 2 with grant flow type" << GRANTFLOW_STR(grantFlowType) << "..."; + o2Msgraph_->setGrantFlow(grantFlowType); + o2Msgraph_->unlink(); + o2Msgraph_->link(); +} + +void MsgraphDemo::getUserPrincipalName() { + if (!o2Msgraph_->linked()) { + qWarning() << "ERROR: Application is not linked!"; + emit linkingFailed(); + return; + } + + QString userInfoURL = QString(MSGRAPH_USER_INFO_URL); + QNetworkRequest request = QNetworkRequest(QUrl(userInfoURL)); + QNetworkAccessManager *mgr = new QNetworkAccessManager(this); + O2Requestor *requestor = new O2Requestor(mgr, o2Msgraph_, this); + requestor->setAddAccessTokenInQuery(false); + requestor->setAccessTokenInAuthenticationHTTPHeaderFormat("Bearer %1"); + requestId_ = requestor->get(request); + connect(requestor, SIGNAL(finished(int, QNetworkReply::NetworkError, QByteArray)), + this, SLOT(onFinished(int, QNetworkReply::NetworkError, QByteArray)) + ); + qDebug() << "Getting user info... Please wait."; +} + +void MsgraphDemo::onOpenBrowser(const QUrl &url) { + QDesktopServices::openUrl(url); +} + +void MsgraphDemo::onCloseBrowser() { +} + +void MsgraphDemo::onLinkedChanged() { + qDebug() << "Link changed!"; +} + +void MsgraphDemo::onLinkingSucceeded() { + O2Msgraph *o2t = qobject_cast(sender()); + if (!o2t->linked()) { + return; + } + QVariantMap extraTokens = o2t->extraTokens(); + if (!extraTokens.isEmpty()) { + emit extraTokensReady(extraTokens); + qDebug() << "Extra tokens in response:"; + foreach (QString key, extraTokens.keys()) { + qDebug() << "\t" << key << ":" << (extraTokens.value(key).toString().left(3) + "..."); + } + } + emit linkingSucceeded(); +} + +void MsgraphDemo::onFinished(int requestId, QNetworkReply::NetworkError error, QByteArray replyData) { + if (requestId != requestId_) + return; + + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; + emit userPrincipalNameFailed(); + return; + } + + QString reply(replyData); + bool errorFound = reply.contains("error"); + if (errorFound) { + qDebug() << "Request failed"; + emit userPrincipalNameFailed(); + return; + } + + QRegExp userPrincipalNameRE("\"userPrincipalName\":\"([^\"]+)\""); + if (userPrincipalNameRE.indexIn(reply) == -1) { + qDebug() << "Can not parse reply:" << reply; + emit userPrincipalNameFailed(); + return; + } + + qInfo() << "userPrincipalName: " << userPrincipalNameRE.cap(1); + emit userPrincipalNameReceived(); +} diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.h b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.h new file mode 100644 index 0000000000..01d8512ce3 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.h @@ -0,0 +1,38 @@ +#ifndef MSGRAPH_H +#define MSGRAPH_H + +#include + +#include "o2msgraph.h" + +class MsgraphDemo : public QObject +{ + Q_OBJECT + +public: + explicit MsgraphDemo(QObject *parent = 0); + +signals: + void extraTokensReady(const QVariantMap &extraTokens); + void linkingFailed(); + void linkingSucceeded(); + void userPrincipalNameReceived(); + void userPrincipalNameFailed(); + +public slots: + void doOAuth(O2::GrantFlow grantFlowType); + void getUserPrincipalName(); + +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + void onCloseBrowser(); + void onFinished(int, QNetworkReply::NetworkError, QByteArray); + +private: + O2Msgraph *o2Msgraph_; + int requestId_; +}; + +#endif // MSGRAPHDEMO_H diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.pro b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.pro new file mode 100644 index 0000000000..32d7ff4746 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphdemo/msgraphdemo.pro @@ -0,0 +1,16 @@ +QT += core gui network + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +include(../../src/src.pri) + +TARGET = msgraphdemo +TEMPLATE = app + +SOURCES += main.cpp \ + msgraphdemo.cpp + +HEADERS += \ + msgraphdemo.h diff --git a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/CMakeLists.txt similarity index 61% copy from core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt copy to core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/CMakeLists.txt index afc3d27060..b2944efb3b 100644 --- a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/CMakeLists.txt @@ -1,43 +1,47 @@ cmake_minimum_required(VERSION 2.8.11) -project( fbexample ) +project( msgraphexternalinterceptorexample ) set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") if(o2_WITH_QT5) # Qt5 packages find their own dependencies. find_package(Qt5Core REQUIRED) find_package(Qt5Widgets REQUIRED) find_package(Qt5Script REQUIRED) find_package(Qt5Network REQUIRED) + find_package(Qt5WebEngine REQUIRED) else(o2_WITH_QT5) set(QT_USE_QTNETWORK true) set(QT_USE_QTSCRIPT true) find_package(Qt4 REQUIRED) endif(o2_WITH_QT5) if (NOT o2_WITH_QT5) include( ${QT_USE_FILE} ) endif(NOT o2_WITH_QT5) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) -set(fb_SRCS +set(msgraph_SRCS main.cpp - fbdemo.cpp + msgraphdemo.cpp + webwindow.cpp + webenginepage.cpp ) if(NOT o2_WITH_QT5) add_definitions(${QT4_DEFINITIONS}) endif(NOT o2_WITH_QT5) -add_executable( fbexample ${fb_SRCS} ) +add_executable( msgraphexternalinterceptorexample ${msgraph_SRCS} ) if(o2_WITH_QT5) - qt5_use_modules( fbexample Core Widgets Network ) - target_link_libraries( fbexample o2 ) + qt5_use_modules( msgraphexternalinterceptorexample Core Widgets Network WebEngineWidgets ) + target_link_libraries( msgraphexternalinterceptorexample o2 ) else(o2_WITH_QT5) - target_link_libraries( fbexample ${QT_LIBRARIES} o2 ) + target_link_libraries( msgraphexternalinterceptorexample ${QT_LIBRARIES} o2 ) endif(o2_WITH_QT5) diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/main.cpp b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/main.cpp new file mode 100644 index 0000000000..c2101ac0f4 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/main.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +#include "msgraphdemo.h" + +class Helper : public QObject { + Q_OBJECT + +public: + Helper() : QObject(), demo_(this) {} + +public slots: + void run() { + connect(&demo_, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(&demo_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(&demo_, SIGNAL(userPrincipalNameReceived()), this, SLOT(onUserPrincipalNameReceived())); + connect(&demo_, SIGNAL(userPrincipalNameFailed()), this, SLOT(onUserPrincipalNameFailed())); + + // Start OAuth + demo_.doOAuth(O2::GrantFlowAuthorizationCode); + } + + void onLinkingFailed() { + qDebug() << "Linking failed!"; + qApp->exit(1); + } + + void onLinkingSucceeded() { + qDebug() << "Linking succeeded!"; + demo_.getUserPrincipalName(); + } + + void onUserPrincipalNameFailed() { + qDebug() << "Error getting userPrincipalName!"; + qApp->exit(1); + } + + void onUserPrincipalNameReceived() { + qDebug() << "UserPrincipalName received!"; + qApp->quit(); + } + +private: + MsgraphDemo demo_; +}; + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QCoreApplication::setOrganizationName("O2"); + QCoreApplication::setApplicationName("Msgraph Example"); + Helper helper; + QTimer::singleShot(0, &helper, SLOT(run())); + return a.exec(); +} + +#include "main.moc" diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphdemo.cpp b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphdemo.cpp new file mode 100644 index 0000000000..04017f9b55 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphdemo.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "msgraphdemo.h" +#include "o0globals.h" +#include "o0settingsstore.h" +#include "o2requestor.h" + +const char MSGRAPH_APP_ID[] = "YOUR_MSGRAPH_APP_ID"; +const char MSGRAPH_REDIRECT_URI[] = "https://login.microsoftonline.com/common/oauth2/nativeclient"; +const char MSGRAPH_SCOPE[] = "User.Read"; +const char MSGRAPH_USER_INFO_URL[] = "https://graph.microsoft.com/v1.0/me"; + +#define QENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) +#define GRANTFLOW_STR(v) QString(QENUM_NAME(O2, GrantFlow, v)) + +MsgraphDemo::MsgraphDemo(QObject *parent) : + QObject(parent), authDialog(NULL), requestId_(0) { + o2Msgraph_ = new O2Msgraph(this); + + o2Msgraph_->setClientId(MSGRAPH_APP_ID); + o2Msgraph_->setUseExternalWebInterceptor(true); + o2Msgraph_->setLocalhostPolicy(MSGRAPH_REDIRECT_URI); + o2Msgraph_->setScope(MSGRAPH_SCOPE); + + + // Create a store object for writing the received tokens + O0SettingsStore *store = new O0SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("msgraph"); + o2Msgraph_->setStore(store); + + connect(o2Msgraph_, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o2Msgraph_, SIGNAL(linkingFailed()), this, SIGNAL(linkingFailed())); + connect(o2Msgraph_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o2Msgraph_, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o2Msgraph_, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); +} + +void MsgraphDemo::doOAuth(O2::GrantFlow grantFlowType) { + qDebug() << "Starting OAuth 2 with grant flow type" << GRANTFLOW_STR(grantFlowType) << "..."; + o2Msgraph_->setGrantFlow(grantFlowType); + o2Msgraph_->unlink(); + o2Msgraph_->link(); +} + +void MsgraphDemo::getUserPrincipalName() { + if (!o2Msgraph_->linked()) { + qWarning() << "ERROR: Application is not linked!"; + emit linkingFailed(); + return; + } + + QString userInfoURL = QString(MSGRAPH_USER_INFO_URL); + QNetworkRequest request = QNetworkRequest(QUrl(userInfoURL)); + QNetworkAccessManager *mgr = new QNetworkAccessManager(this); + O2Requestor *requestor = new O2Requestor(mgr, o2Msgraph_, this); + requestor->setAddAccessTokenInQuery(false); + requestor->setAccessTokenInAuthenticationHTTPHeaderFormat("Bearer %1"); + requestId_ = requestor->get(request); + connect(requestor, SIGNAL(finished(int, QNetworkReply::NetworkError, QByteArray)), + this, SLOT(onFinished(int, QNetworkReply::NetworkError, QByteArray)) + ); + qDebug() << "Getting user info... Please wait."; +} + +void MsgraphDemo::onOpenBrowser(const QUrl &url) { + if (authDialog == Q_NULLPTR) { + authDialog = new WebWindow(QSize(650, 600), url, MSGRAPH_REDIRECT_URI, false); + QObject::connect(authDialog, SIGNAL(callbackCalled(const QString &)), this, SLOT(onAuthWindowCallbackCalled(const QString &))); + QObject::connect(authDialog, SIGNAL(windowClosed()), this, SLOT(onAuthWindowClosed())); + authDialog->exec(); + } +} + +void MsgraphDemo::onAuthWindowCallbackCalled(const QString &inURLString) +{ + if(o2Msgraph_ != NULL) + { + QUrl getTokenUrl(inURLString); + QUrlQuery query(getTokenUrl); + QList< QPair > tokens = query.queryItems(); + + 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().toLatin1())); + QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed().toLatin1())); + queryParams.insert(key, value); + } + + o2Msgraph_->onVerificationReceived(queryParams); + } +} + +void MsgraphDemo::onAuthWindowClosed() +{ + if (authDialog != Q_NULLPTR) + { + authDialog->close(); + delete authDialog; + } +} + +void MsgraphDemo::onCloseBrowser() { +} + +void MsgraphDemo::onLinkedChanged() { + qDebug() << "Link changed!"; +} + +void MsgraphDemo::onLinkingSucceeded() { + O2Msgraph *o2t = qobject_cast(sender()); + if (!o2t->linked()) { + return; + } + QVariantMap extraTokens = o2t->extraTokens(); + if (!extraTokens.isEmpty()) { + emit extraTokensReady(extraTokens); + qDebug() << "Extra tokens in response:"; + foreach (QString key, extraTokens.keys()) { + qDebug() << "\t" << key << ":" << (extraTokens.value(key).toString().left(3) + "..."); + } + } + emit linkingSucceeded(); +} + +void MsgraphDemo::onFinished(int requestId, QNetworkReply::NetworkError error, QByteArray replyData) { + if (requestId != requestId_) + return; + + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; + emit userPrincipalNameFailed(); + return; + } + + QString reply(replyData); + bool errorFound = reply.contains("error"); + if (errorFound) { + qDebug() << "Request failed"; + emit userPrincipalNameFailed(); + return; + } + + QRegExp userPrincipalNameRE("\"userPrincipalName\":\"([^\"]+)\""); + if (userPrincipalNameRE.indexIn(reply) == -1) { + qDebug() << "Can not parse reply:" << reply; + emit userPrincipalNameFailed(); + return; + } + + qInfo() << "userPrincipalName: " << userPrincipalNameRE.cap(1); + emit userPrincipalNameReceived(); +} diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphdemo.h b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphdemo.h new file mode 100644 index 0000000000..b9971a5868 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphdemo.h @@ -0,0 +1,42 @@ +#ifndef MSGRAPHDEMO_H +#define MSGRAPHDEMO_H + +#include + +#include "o2msgraph.h" +#include "webwindow.h" + +class MsgraphDemo : public QObject +{ + Q_OBJECT + +public: + explicit MsgraphDemo(QObject *parent = 0); + +signals: + void extraTokensReady(const QVariantMap &extraTokens); + void linkingFailed(); + void linkingSucceeded(); + void userPrincipalNameReceived(); + void userPrincipalNameFailed(); + +public slots: + void doOAuth(O2::GrantFlow grantFlowType); + void getUserPrincipalName(); + +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + void onAuthWindowCallbackCalled(const QString &inURLString); + void onAuthWindowClosed(); + void onCloseBrowser(); + void onFinished(int, QNetworkReply::NetworkError, QByteArray); + +private: + O2Msgraph *o2Msgraph_; + WebWindow* authDialog; + int requestId_; +}; + +#endif // MSGRAPHDEMO_H diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphexternalinterceptordemo.pro b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphexternalinterceptordemo.pro new file mode 100644 index 0000000000..fabb47fe34 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/msgraphexternalinterceptordemo.pro @@ -0,0 +1,23 @@ +QT += core gui webenginewidgets + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +DEFINES += O0_EXPORT= +include(../../src/src.pri) + +TARGET = msgraphdemo +TEMPLATE = app + +SOURCES += main.cpp \ + msgraphdemo.cpp \ + webenginepage.cpp \ + webwindow.cpp + +HEADERS += \ + msgraphdemo.h \ + webenginepage.h \ + webwindow.h + +FORMS += webwindow.ui diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webenginepage.cpp b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webenginepage.cpp new file mode 100644 index 0000000000..906e9e1bbe --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webenginepage.cpp @@ -0,0 +1,93 @@ + +#include "webenginepage.h" +#include "webwindow.h" + +WebEnginePage::~WebEnginePage() +{ + for(WebWindow *createdWindow : mCreatedWindows) + { + createdWindow->close(); + createdWindow->deleteLater(); + } + + mCreatedWindows.clear(); +} + +bool WebEnginePage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType /*type*/, bool /*isMainFrame*/) +{ + QString urlString = url.toString(); + if(mRedirectURLString.length() > 0 && + (urlString.length() >= mRedirectURLString.length()) && + (urlString.left(mRedirectURLString.length()) == mRedirectURLString)) + { + emit callbackCatched(url.toString()); + return false; + } + + return true; +} + +QWebEnginePage *WebEnginePage::createWindow(WebWindowType type) +{ + switch(type) + { + case QWebEnginePage::WebBrowserTab: + case QWebEnginePage::WebBrowserBackgroundTab: + case QWebEnginePage::WebBrowserWindow: + case QWebEnginePage::WebDialog: + { + WebWindow *webViewDialog = new WebWindow(QSize(600, 500), QUrl(), mRedirectURLString, false); + QObject::connect(webViewDialog, SIGNAL(callbackCalled(const QString &)), this, SLOT(onAuthWindowCallbackCalled(const QString &))); + QObject::connect(webViewDialog, SIGNAL(windowClosed()), this, SLOT(onCreatedWindowClosed())); + QObject::connect(webViewDialog->GetWebEnginePage(), SIGNAL(windowCloseRequested()), this, SLOT(onWindowCloseRequested())); + mCreatedWindows.push_back(webViewDialog); + webViewDialog->open(); + return webViewDialog->GetWebEnginePage(); + } + } + + return NULL; +} + +void WebEnginePage::onAuthWindowCallbackCalled(const QString &inURLString) +{ + emit callbackCatched(inURLString); +} + +void WebEnginePage::onCreatedWindowClosed() +{ + WebWindow *createdWindow = qobject_cast(sender()); + if (createdWindow != Q_NULLPTR) + { + std::vector::iterator iterator = std::find(mCreatedWindows.begin(), mCreatedWindows.end(), createdWindow); + if(iterator != mCreatedWindows.end()) + { + mCreatedWindows.erase(iterator); + createdWindow->close(); + createdWindow->deleteLater(); + } + } +} + +void WebEnginePage::onWindowCloseRequested() +{ + QWebEnginePage *webEngine = qobject_cast(sender()); + if (webEngine != Q_NULLPTR) + { + for(WebWindow *createdWindow : mCreatedWindows) + { + if(createdWindow->GetWebEnginePage() == webEngine) + { + std::vector::iterator iterator = std::find(mCreatedWindows.begin(), mCreatedWindows.end(), createdWindow); + if(iterator != mCreatedWindows.end()) + { + mCreatedWindows.erase(iterator); + createdWindow->close(); + createdWindow->deleteLater(); + } + + break; + } + } + } +} diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webenginepage.h b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webenginepage.h new file mode 100644 index 0000000000..4b9ec42768 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webenginepage.h @@ -0,0 +1,35 @@ + +#pragma once + +#include + +class WebWindow; + +class WebEnginePage : public QWebEnginePage +{ + Q_OBJECT + +public: + + WebEnginePage(QWebEngineProfile *inProfile, const QString &inRedirectURLString) : QWebEnginePage(inProfile), mRedirectURLString(inRedirectURLString) { } + + virtual ~WebEnginePage(); + + bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame); + +protected: + QWebEnginePage *createWindow(WebWindowType type); + +signals: + void callbackCatched(const QString &inURLString); + +private slots: + void onCreatedWindowClosed(); + void onWindowCloseRequested(); + void onAuthWindowCallbackCalled(const QString &inURLString); + +private: + QString mRedirectURLString; + std::vector mCreatedWindows; +}; + diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.cpp b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.cpp new file mode 100644 index 0000000000..bc5f850a0b --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.cpp @@ -0,0 +1,96 @@ +#include "webwindow.h" +#include "ui_webwindow.h" +#include "webenginepage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +WebWindow::WebWindow(QSize inWindowSize, QUrl inLoginURL, QString inRedirectURLString, bool inAutoclose) : + QDialog(Q_NULLPTR), + ui(new Ui::WebWindow), + mAutoclose(inAutoclose) +{ + ui->setupUi(this); + setFocus(); + setFixedSize(inWindowSize); + + mWebView = new QWebEngineView(this); + mWebView->settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); + + mWebEngineProfile = new QWebEngineProfile(); + + mWebEnginePage = new WebEnginePage(mWebEngineProfile, inRedirectURLString); + QObject::connect(mWebEnginePage, SIGNAL(callbackCatched(const QString &)), this, SLOT(onCallbackCatched(const QString &))); + + mWebView->setPage(mWebEnginePage); + + QRect rect_map = QRect(1, 1, width() - 2, height() - 2); + mWebView->setGeometry(rect_map); + mWebView->setObjectName("webView"); + + mWebView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + if(!inLoginURL.isEmpty()) + { + mWebView->page()->profile()->cookieStore()->deleteAllCookies(); + } + + // Load the URL + mWebView->setUrl(inLoginURL); +} + +WebWindow::~WebWindow() +{ + mWebView->setPage(Q_NULLPTR); + delete mWebEnginePage; + + delete mWebEngineProfile; + + mWebView->close(); + delete mWebView; + + delete ui; +} + +void WebWindow::closeEvent(QCloseEvent *) +{ + emit (windowClosed()); +} + +void WebWindow::onCallbackCatched(const QString &inURLString) +{ + mCatchedOAuthURL = inURLString; + QTimer::singleShot(100, this, SLOT(onCallbackCatchedSafe())); +} + +void WebWindow::onCallbackCatchedSafe() +{ + if(mAutoclose) + { + QUrl getTokenUrl(mCatchedOAuthURL); + QUrlQuery query(getTokenUrl); + QList< QPair > tokens = query.queryItems(); + + QMultiMap queryParams; + QPair tokenPair; + foreach (tokenPair, tokens) { + QString key = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.first.trimmed().toLatin1())); + QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed().toLatin1())); + queryParams.insert(key, value); + } + + resultStr = queryParams.value("code", ""); + close(); + } + else + { + emit callbackCalled(mCatchedOAuthURL); + } +} + + diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.h b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.h new file mode 100644 index 0000000000..f87a503e48 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.h @@ -0,0 +1,49 @@ + +#pragma once + +#include +#include + +namespace Ui { +class WebWindow; +} + +class WebWindow : public QDialog +{ + Q_OBJECT + +public: + explicit WebWindow(QSize inWindowSize, QUrl inLoginURL, QString inRedirectURLString, bool inAutoclose); + ~WebWindow(); + + QString resultStr; + + QWebEnginePage *GetWebEnginePage() { return mWebEnginePage; } + +protected: + void closeEvent(QCloseEvent *); + +signals: + void windowClosed(); + void callbackCalled(const QString &inURLString); + +private slots: + void onCallbackCatched(const QString &inURLString); + void onCallbackCatchedSafe(); + +private: + Ui::WebWindow *ui; + + // Profile to not store cookies and cache data to the disk + QWebEngineProfile *mWebEngineProfile; + + // Webengine page using the Off-the-record profile + QWebEnginePage *mWebEnginePage; + + // Webview + QWebEngineView* mWebView; + + QString mCatchedOAuthURL; + bool mAutoclose; +}; + diff --git a/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.ui b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.ui new file mode 100644 index 0000000000..d874d6b5d9 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/msgraphexternalinterceptordemo/webwindow.ui @@ -0,0 +1,16 @@ + + + WebWindow + + + + 0 + 0 + 600 + 500 + + + + + + diff --git a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt b/core/libs/dplugins/webservices/o2/examples/vimeodemo/CMakeLists.txt similarity index 70% copy from core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt copy to core/libs/dplugins/webservices/o2/examples/vimeodemo/CMakeLists.txt index afc3d27060..e319fec17e 100644 --- a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/examples/vimeodemo/CMakeLists.txt @@ -1,43 +1,44 @@ cmake_minimum_required(VERSION 2.8.11) -project( fbexample ) +project( vimeoexample ) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") if(o2_WITH_QT5) # Qt5 packages find their own dependencies. find_package(Qt5Core REQUIRED) - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Script REQUIRED) + find_package(Qt5Gui REQUIRED) find_package(Qt5Network REQUIRED) + find_package(Qt5Widgets REQUIRED) else(o2_WITH_QT5) set(QT_USE_QTNETWORK true) set(QT_USE_QTSCRIPT true) find_package(Qt4 REQUIRED) endif(o2_WITH_QT5) if (NOT o2_WITH_QT5) include( ${QT_USE_FILE} ) endif(NOT o2_WITH_QT5) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) -set(fb_SRCS +set(vimeo_SRCS main.cpp - fbdemo.cpp + vimeodemo.cpp + vimeodemo.h ) if(NOT o2_WITH_QT5) add_definitions(${QT4_DEFINITIONS}) endif(NOT o2_WITH_QT5) -add_executable( fbexample ${fb_SRCS} ) +add_executable( vimeoexample ${vimeo_SRCS} ) if(o2_WITH_QT5) - qt5_use_modules( fbexample Core Widgets Network ) - target_link_libraries( fbexample o2 ) + qt5_use_modules( vimeoexample Core Gui Network Widgets ) + target_link_libraries( vimeoexample o2 ) else(o2_WITH_QT5) - target_link_libraries( fbexample ${QT_LIBRARIES} o2 ) + target_link_libraries( vimeoexample ${QT_LIBRARIES} o2 ) endif(o2_WITH_QT5) diff --git a/core/libs/dplugins/webservices/o2/examples/vimeodemo/main.cpp b/core/libs/dplugins/webservices/o2/examples/vimeodemo/main.cpp new file mode 100644 index 0000000000..9886b57f1d --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/vimeodemo/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +#include +#include + +#include "vimeodemo.h" + +const char OPT_OAUTH_CODE[] = "-o"; + +class Helper : public QObject { + Q_OBJECT + +public: + Helper() : QObject(), demo_(this), waitForMsg_(false), msg_(QString()) {} + +public slots: + void run() { + connect(&demo_, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(&demo_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(&demo_, SIGNAL(userNameReceived()), this, SLOT(onUserNameReceived())); + connect(&demo_, SIGNAL(userNameFailed()), this, SLOT(onUserNameFailed())); + + demo_.doOAuth(O2::GrantFlowAuthorizationCode); + } + + void onLinkingFailed() { + qDebug() << "Linking failed!"; + qApp->exit(1); + } + + void onLinkingSucceeded() { + qDebug() << "Linking succeeded!"; + demo_.getUserName(); + } + + void onUserNameFailed() { + qDebug() << "Error getting user name!"; + qApp->exit(1); + } + + void onUserNameReceived() { + qDebug() << "User namereceived!"; + qApp->quit(); + } + +private: + VimeoDemo demo_; + bool waitForMsg_; + QString msg_; +}; + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QCoreApplication::setOrganizationName("O2"); + QCoreApplication::setApplicationName("Vimeo Test"); + Helper helper; + QTimer::singleShot(0, &helper, SLOT(run())); + return a.exec(); +} + +#include "main.moc" diff --git a/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.cpp b/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.cpp new file mode 100644 index 0000000000..adfb373607 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "vimeodemo.h" +#include "o0globals.h" +#include "o0settingsstore.h" +#include "o2requestor.h" + +const char VIMEO_APP_KEY[] = "YOUR_VIMEO_APP_KEY"; +const char VIMEO_APP_SECRET[] = "YOUT_VIMEO_APP_SECRET"; +const char VIMEO_SCOPE[] = "public"; +const char VIMEO_USER_INFO_URL[] = "https://api.vimeo.com/me"; + +const int localPort = 8888; + +#define QENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) +#define GRANTFLOW_STR(v) QString(QENUM_NAME(O2, GrantFlow, v)) + +VimeoDemo::VimeoDemo(QObject *parent) : + QObject(parent), requestId_(0) { + o2Vimeo_ = new O2Vimeo(this); + + o2Vimeo_->setClientId(VIMEO_APP_KEY); + o2Vimeo_->setClientSecret(VIMEO_APP_SECRET); + o2Vimeo_->setLocalPort(localPort); + o2Vimeo_->setScope(VIMEO_SCOPE); + + // Create a store object for writing the received tokens + O0SettingsStore *store = new O0SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("google"); + o2Vimeo_->setStore(store); + + connect(o2Vimeo_, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o2Vimeo_, SIGNAL(linkingFailed()), this, SIGNAL(linkingFailed())); + connect(o2Vimeo_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o2Vimeo_, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o2Vimeo_, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); +} + +void VimeoDemo::doOAuth(O2::GrantFlow grantFlowType) { + qDebug() << "Starting OAuth 2 with grant flow type" << GRANTFLOW_STR(grantFlowType) << "..."; + o2Vimeo_->setGrantFlow(grantFlowType); + o2Vimeo_->unlink(); + o2Vimeo_->link(); +} + +void VimeoDemo::getUserName() { + if (!o2Vimeo_->linked()) { + qWarning() << "ERROR: Application is not linked!"; + emit linkingFailed(); + return; + } + + QString userInfoURL = QString(VIMEO_USER_INFO_URL); + QNetworkRequest request = QNetworkRequest(QUrl(userInfoURL)); + QNetworkAccessManager *mgr = new QNetworkAccessManager(this); + O2Requestor *requestor = new O2Requestor(mgr, o2Vimeo_, this); + requestId_ = requestor->get(request); + connect(requestor, SIGNAL(finished(int, QNetworkReply::NetworkError, QByteArray)), + this, SLOT(onFinished(int, QNetworkReply::NetworkError, QByteArray)) + ); + qDebug() << "Getting user channel info... Please wait."; +} + +void VimeoDemo::onOpenBrowser(const QUrl &url) { + QDesktopServices::openUrl(url); +} + +void VimeoDemo::onCloseBrowser() { +} + +void VimeoDemo::onLinkedChanged() { + qDebug() << "Link changed!"; +} + +void VimeoDemo::onLinkingSucceeded() { + if (!o2Vimeo_->linked()) { + return; + } + QVariantMap extraTokens = o2Vimeo_->extraTokens(); + if (!extraTokens.isEmpty()) { + emit extraTokensReady(extraTokens); + qDebug() << "Extra tokens in response:"; + foreach (QString key, extraTokens.keys()) { + qDebug() << "\t" << key << ":" << (extraTokens.value(key).toString().left(3) + "..."); + } + } + emit linkingSucceeded(); +} + +void VimeoDemo::onFinished(int requestId, QNetworkReply::NetworkError error, QByteArray replyData) { + if (requestId != requestId_) + return; + + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; + emit userNameFailed(); + return; + } + + QString reply(replyData); + bool errorFound = reply.contains("error"); + if (errorFound) { + qDebug() << "Request failed"; + emit userNameFailed(); + return; + } + + QRegExp nameRE("\"name\":\"([^\"]+)\""); + if (nameRE.indexIn(reply) == -1) { + qDebug() << "Can not parse reply:" << reply; + emit userNameFailed(); + return; + } + + qInfo() << "User name: " << nameRE.cap(1); + emit userNameReceived(); +} diff --git a/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.h b/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.h new file mode 100644 index 0000000000..8d53502230 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.h @@ -0,0 +1,38 @@ +#ifndef VMDEMO_H +#define VMDEMO_H + +#include + +#include "o2vimeo.h" + +class VimeoDemo : public QObject +{ + Q_OBJECT + +public: + explicit VimeoDemo(QObject *parent = 0); + +signals: + void extraTokensReady(const QVariantMap &extraTokens); + void linkingFailed(); + void linkingSucceeded(); + void userNameReceived(); + void userNameFailed(); + +public slots: + void doOAuth(O2::GrantFlow grantFlowType); + void getUserName(); + +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + void onCloseBrowser(); + void onFinished(int, QNetworkReply::NetworkError, QByteArray); + +private: + O2Vimeo *o2Vimeo_; + int requestId_; +}; + +#endif // VMDEMO_H diff --git a/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.pro b/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.pro new file mode 100644 index 0000000000..6f09825335 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/vimeodemo/vimeodemo.pro @@ -0,0 +1,16 @@ +QT += core gui network + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +include(../../src/src.pri) + +TARGET = vimeodemo +TEMPLATE = app + +SOURCES += main.cpp \ + vimeodemo.cpp + +HEADERS += \ + vimeodemo.h diff --git a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt b/core/libs/dplugins/webservices/o2/examples/youtubedemo/CMakeLists.txt similarity index 71% copy from core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt copy to core/libs/dplugins/webservices/o2/examples/youtubedemo/CMakeLists.txt index afc3d27060..974aa9cc37 100644 --- a/core/libs/dplugins/webservices/o2/examples/facebookdemo/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/examples/youtubedemo/CMakeLists.txt @@ -1,43 +1,44 @@ cmake_minimum_required(VERSION 2.8.11) -project( fbexample ) +project( ytexample ) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") if(o2_WITH_QT5) # Qt5 packages find their own dependencies. find_package(Qt5Core REQUIRED) - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Script REQUIRED) + find_package(Qt5Gui REQUIRED) find_package(Qt5Network REQUIRED) + find_package(Qt5Widgets REQUIRED) else(o2_WITH_QT5) set(QT_USE_QTNETWORK true) set(QT_USE_QTSCRIPT true) find_package(Qt4 REQUIRED) endif(o2_WITH_QT5) if (NOT o2_WITH_QT5) include( ${QT_USE_FILE} ) endif(NOT o2_WITH_QT5) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) -set(fb_SRCS +set(yt_SRCS main.cpp - fbdemo.cpp + ytdemo.cpp + ytdemo.h ) if(NOT o2_WITH_QT5) add_definitions(${QT4_DEFINITIONS}) endif(NOT o2_WITH_QT5) -add_executable( fbexample ${fb_SRCS} ) +add_executable( ytexample ${yt_SRCS} ) if(o2_WITH_QT5) - qt5_use_modules( fbexample Core Widgets Network ) - target_link_libraries( fbexample o2 ) + qt5_use_modules( ytexample Core Gui Network Widgets ) + target_link_libraries( ytexample o2 ) else(o2_WITH_QT5) - target_link_libraries( fbexample ${QT_LIBRARIES} o2 ) + target_link_libraries( ytexample ${QT_LIBRARIES} o2 ) endif(o2_WITH_QT5) diff --git a/core/libs/dplugins/webservices/o2/examples/youtubedemo/main.cpp b/core/libs/dplugins/webservices/o2/examples/youtubedemo/main.cpp new file mode 100644 index 0000000000..5c91f8e985 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/youtubedemo/main.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +#include "ytdemo.h" + +const char OPT_OAUTH_CODE[] = "-o"; + +class Helper : public QObject { + Q_OBJECT + +public: + Helper() : QObject(), ytdemo_(this), waitForMsg_(false), msg_(QString()) {} + +public slots: + void run() { + connect(&ytdemo_, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(&ytdemo_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(&ytdemo_, SIGNAL(channelInfoReceived()), this, SLOT(onChannelInfoReceived())); + connect(&ytdemo_, SIGNAL(channelInfoFailed()), this, SLOT(onChannelInfoFailed())); + + ytdemo_.doOAuth(O2::GrantFlowAuthorizationCode); + } + + void onLinkingFailed() { + qDebug() << "Linking failed!"; + qApp->exit(1); + } + + void onLinkingSucceeded() { + qDebug() << "Linking succeeded!"; + ytdemo_.getUserChannelInfo(); + } + + void onChannelInfoFailed() { + qDebug() << "Error getting channel info!"; + qApp->exit(1); + } + + void onChannelInfoReceived() { + qDebug() << "Channel info received!"; + qApp->quit(); + } + +private: + YTDemo ytdemo_; + bool waitForMsg_; + QString msg_; +}; + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QCoreApplication::setOrganizationName("O2"); + QCoreApplication::setApplicationName("YouTube Test"); + Helper helper; + QTimer::singleShot(0, &helper, SLOT(run())); + return a.exec(); +} + +#include "main.moc" diff --git a/core/libs/dplugins/webservices/o2/examples/youtubedemo/youtubedemo.pro b/core/libs/dplugins/webservices/o2/examples/youtubedemo/youtubedemo.pro new file mode 100644 index 0000000000..ba67eedccb --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/youtubedemo/youtubedemo.pro @@ -0,0 +1,16 @@ +QT += core gui network + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +include(../../src/src.pri) + +TARGET = youtubedemo +TEMPLATE = app + +SOURCES += main.cpp \ + ytdemo.cpp + +HEADERS += \ + ytdemo.h diff --git a/core/libs/dplugins/webservices/o2/examples/youtubedemo/ytdemo.cpp b/core/libs/dplugins/webservices/o2/examples/youtubedemo/ytdemo.cpp new file mode 100644 index 0000000000..4293e35304 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/youtubedemo/ytdemo.cpp @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include +#include + +#include "ytdemo.h" +#include "o0globals.h" +#include "o0settingsstore.h" +#include "o2requestor.h" + +const char GOOGLE_APP_KEY[] = "YOUR_YOUTUBE_APP_KEY"; +const char GOOGLE_APP_SECRET[] = "YOUR_YOUTUBE_APP_SECRET"; +const char YOUTUBE_SCOPE[] = "https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube"; +const char YOUTUBE_CHANNELS_LIST_URL[] = "https://www.googleapis.com/youtube/v3/channels?part=status&mine=true"; + +const int localPort = 8888; + +#define QENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) +#define GRANTFLOW_STR(v) QString(QENUM_NAME(O2, GrantFlow, v)) + +YTDemo::YTDemo(QObject *parent) : + QObject(parent), requestId_(0) { + o2Google_ = new O2Google(this); + + o2Google_->setClientId(GOOGLE_APP_KEY); + o2Google_->setClientSecret(GOOGLE_APP_SECRET); + o2Google_->setLocalPort(localPort); + o2Google_->setScope(YOUTUBE_SCOPE); + + // Create a store object for writing the received tokens + O0SettingsStore *store = new O0SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("google"); + o2Google_->setStore(store); + + connect(o2Google_, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o2Google_, SIGNAL(linkingFailed()), this, SIGNAL(linkingFailed())); + connect(o2Google_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o2Google_, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o2Google_, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); +} + +void YTDemo::doOAuth(O2::GrantFlow grantFlowType) { + qDebug() << "Starting OAuth 2 with grant flow type" << GRANTFLOW_STR(grantFlowType) << "..."; + o2Google_->setGrantFlow(grantFlowType); + o2Google_->unlink(); + o2Google_->link(); +} + +void YTDemo::getUserChannelInfo() { + if (!o2Google_->linked()) { + qWarning() << "ERROR: Application is not linked!"; + emit linkingFailed(); + return; + } + + QString channelsListUrl = QString(YOUTUBE_CHANNELS_LIST_URL); + QNetworkRequest request = QNetworkRequest(QUrl(channelsListUrl)); + QNetworkAccessManager *mgr = new QNetworkAccessManager(this); + O2Requestor *requestor = new O2Requestor(mgr, o2Google_, this); + requestId_ = requestor->get(request); + connect(requestor, SIGNAL(finished(int, QNetworkReply::NetworkError, QByteArray)), + this, SLOT(onFinished(int, QNetworkReply::NetworkError, QByteArray)) + ); + qDebug() << "Getting user channel info... Please wait."; +} + +void YTDemo::onOpenBrowser(const QUrl &url) { + QDesktopServices::openUrl(url); +} + +void YTDemo::onCloseBrowser() { +} + +void YTDemo::onLinkedChanged() { + qDebug() << "Link changed!"; +} + +void YTDemo::onLinkingSucceeded() { + O2Google *o2t = qobject_cast(sender()); + if (!o2t->linked()) { + return; + } + QVariantMap extraTokens = o2t->extraTokens(); + if (!extraTokens.isEmpty()) { + emit extraTokensReady(extraTokens); + qDebug() << "Extra tokens in response:"; + foreach (QString key, extraTokens.keys()) { + qDebug() << "\t" << key << ":" << (extraTokens.value(key).toString().left(3) + "..."); + } + } + emit linkingSucceeded(); +} + +void YTDemo::onFinished(int requestId, QNetworkReply::NetworkError error, QByteArray replyData) { + if (requestId != requestId_) + return; + + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; + emit channelInfoFailed(); + return; + } + + QString reply(replyData); + bool errorFound = reply.contains("error"); + if (errorFound) { + qDebug() << "Request failed"; + emit channelInfoFailed(); + return; + } + + qInfo() << "Channel info: " << reply; + emit channelInfoReceived(); +} diff --git a/core/libs/dplugins/webservices/o2/examples/youtubedemo/ytdemo.h b/core/libs/dplugins/webservices/o2/examples/youtubedemo/ytdemo.h new file mode 100644 index 0000000000..6be20af505 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/examples/youtubedemo/ytdemo.h @@ -0,0 +1,38 @@ +#ifndef YTDEMO_H +#define YTDEMO_H + +#include + +#include "o2google.h" + +class YTDemo : public QObject +{ + Q_OBJECT + +public: + explicit YTDemo(QObject *parent = 0); + +signals: + void extraTokensReady(const QVariantMap &extraTokens); + void linkingFailed(); + void linkingSucceeded(); + void channelInfoReceived(); + void channelInfoFailed(); + +public slots: + void doOAuth(O2::GrantFlow grantFlowType); + void getUserChannelInfo(); + +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + void onCloseBrowser(); + void onFinished(int, QNetworkReply::NetworkError, QByteArray); + +private: + O2Google *o2Google_; + int requestId_; +}; + +#endif // YTDEMO_H diff --git a/core/libs/dplugins/webservices/o2/src/CMakeLists.txt b/core/libs/dplugins/webservices/o2/src/CMakeLists.txt index cad487b8ef..2fc3a37eb3 100644 --- a/core/libs/dplugins/webservices/o2/src/CMakeLists.txt +++ b/core/libs/dplugins/webservices/o2/src/CMakeLists.txt @@ -1,240 +1,269 @@ cmake_minimum_required(VERSION 2.8.11) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") if(o2_WITH_QT5) find_package(Qt5 COMPONENTS Core Network REQUIRED) else(o2_WITH_QT5) set(QT_USE_QTNETWORK true) set(QT_USE_QTSCRIPT true) find_package(Qt4 REQUIRED) endif(o2_WITH_QT5) #find_package(QJson REQUIRED) if (NOT o2_WITH_QT5) include( ${QT_USE_FILE} ) endif(NOT o2_WITH_QT5) set( o2_SRCS o2.cpp o2reply.cpp o2replyserver.cpp o2requestor.cpp o2simplecrypt.cpp o0settingsstore.cpp o0baseauth.cpp ) set( o2_HDRS o2.h o2reply.h o2replyserver.h o2requestor.h o0abstractstore.h o0baseauth.h o0export.h o0globals.h o0requestparameter.h o0settingsstore.h o0simplecrypt.h ) if(o2_WITH_OAUTH1) set( o2_SRCS ${o2_SRCS} o1.cpp o1requestor.cpp o1timedreply.cpp ) set( o2_HDRS ${o2_HDRS} o1.h o1requestor.h o1timedreply.h o1freshbooks.h ) endif(o2_WITH_OAUTH1) if(o2_WITH_TWITTER) set( o2_SRCS ${o2_SRCS} oxtwitter.cpp ) set( o2_HDRS ${o2_HDRS} o1twitter.h oxtwitter.h ) endif(o2_WITH_TWITTER) if(o2_WITH_DROPBOX) set( o2_HDRS ${o2_HDRS} o1dropbox.h ) endif(o2_WITH_DROPBOX) if(o2_WITH_GOOGLE) set( o2_SRCS ${o2_SRCS} o2gft.cpp o2google.cpp ) set( o2_HDRS ${o2_HDRS} o2gft.h o2google.h ) endif(o2_WITH_GOOGLE) +if(o2_WITH_VIMEO) + set( o2_SRCS + ${o2_SRCS} + o2vimeo.cpp + ) + set( o2_HDRS + ${o2_HDRS} + o2vimeo.h + ) +endif(o2_WITH_VIMEO) + if(o2_WITH_FACEBOOK) set( o2_SRCS ${o2_SRCS} o2facebook.cpp ) set( o2_HDRS ${o2_HDRS} o2facebook.h ) endif(o2_WITH_FACEBOOK) if(o2_WITH_SKYDRIVE) set( o2_SRCS ${o2_SRCS} o2skydrive.cpp ) set( o2_HDRS ${o2_HDRS} o2skydrive.h ) endif(o2_WITH_SKYDRIVE) if(o2_WITH_FLICKR) set( o2_HDRS ${o2_HDRS} o1flickr.h ) endif(o2_WITH_FLICKR) if(o2_WITH_HUBIC) set( o2_SRCS ${o2_SRCS} o2hubic.cpp ) set( o2_HDRS ${o2_HDRS} o2hubic.h ) endif(o2_WITH_HUBIC) if(o2_WITH_SPOTIFY) set( o2_SRCS ${o2_SRCS} o2spotify.cpp ) set( o2_HDRS ${o2_HDRS} o2spotify.h ) endif(o2_WITH_SPOTIFY) if(o2_WITH_SURVEYMONKEY) set( o2_SRCS ${o2_SRCS} o2surveymonkey.cpp ) set( o2_HDRS ${o2_HDRS} o2surveymonkey.h ) endif(o2_WITH_SURVEYMONKEY) if(o2_WITH_SMUGMUG) set( o2_SRCS ${o2_SRCS} o1smugmug.cpp ) set( o2_HDRS ${o2_HDRS} o1smugmug.h ) endif(o2_WITH_SMUGMUG) +if(o2_WITH_MSGRAPH) + set( o2_SRCS + ${o2_SRCS} + o2msgraph.cpp + ) + set( o2_HDRS + ${o2_HDRS} + o2msgraph.h + ) +endif(o2_WITH_MSGRAPH) if(o2_WITH_KEYCHAIN) if (Qt5Core_DIR) find_package(Qt5Keychain REQUIRED) else() find_package(QtKeychain REQUIRED) endif() if(QTKEYCHAIN_FOUND OR QT5KEYCHAIN_FOUND) MESSAGE("Found QTKeychain") list(APPEND LINK_TARGETS ${QTKEYCHAIN_LIBRARY}) include_directories(${QTKEYCHAIN_INCLUDE_DIR}) set( o2_SRCS ${o2_SRCS} o0keychainstore.cpp ) set( o2_HDRS ${o2_HDRS} o0keychainstore.h ) else() MESSAGE("Qt5Keychain or QtKeychain is required") endif() endif(o2_WITH_KEYCHAIN) if(NOT o2_WITH_QT5) add_definitions(${QT4_DEFINITIONS}) endif(NOT o2_WITH_QT5) if(BUILD_SHARED_LIBS AND APPLE AND POLICY CMP0042) # in CMake >= 2.8.12 cmake_policy(SET CMP0042 OLD) set(CMAKE_MACOSX_RPATH OFF) # don't embed @rpath in install name endif(BUILD_SHARED_LIBS AND APPLE AND POLICY CMP0042) add_library( o2 ${o2_SRCS} ${o2_HDRS} ) +if(BUILD_SHARED_LIBS) + add_definitions( -DO2_SHARED_LIB ) +endif(BUILD_SHARED_LIBS) + if(o2_WITH_QT5) target_link_libraries( o2 Qt5::Core Qt5::Network ${LINK_TARGETS}) else(o2_WITH_QT5) target_link_libraries( o2 ${QT_LIBRARIES} ${LINK_TARGETS}) endif(o2_WITH_QT5) if(BUILD_SHARED_LIBS) if(APPLE) set_target_properties(o2 PROPERTIES INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib ) endif(APPLE) set_target_properties(o2 PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${API_VERSION} ) else(BUILD_SHARED_LIBS) # needed for statically linked o2 in shared libs on x86_64 set_target_properties(o2 PROPERTIES POSITION_INDEPENDENT_CODE TRUE ) endif(BUILD_SHARED_LIBS) install(TARGETS o2 RUNTIME DESTINATION bin LIBRARY DESTINATION lib${o2_LIB_SUFFIX} ARCHIVE DESTINATION lib${o2_LIB_SUFFIX} ) install(FILES ${o2_HDRS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/o2 ) message(STATUS "Writing pkg-config file...") configure_file(${CMAKE_SOURCE_DIR}/o2.pc.cmake ${CMAKE_BINARY_DIR}/o2.pc @ONLY) install(FILES ${CMAKE_BINARY_DIR}/o2.pc DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${o2_LIB_SUFFIX}/pkgconfig/") + +configure_file(${CMAKE_SOURCE_DIR}/o2-config.h.cmake ${CMAKE_BINARY_DIR}/o2-config.h @ONLY) +install(FILES ${CMAKE_BINARY_DIR}/o2-config.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include/o2") + diff --git a/core/libs/dplugins/webservices/o2/src/o0abstractstore.h b/core/libs/dplugins/webservices/o2/src/o0abstractstore.h index 28071180c6..d4b4309aff 100644 --- a/core/libs/dplugins/webservices/o2/src/o0abstractstore.h +++ b/core/libs/dplugins/webservices/o2/src/o0abstractstore.h @@ -1,24 +1,23 @@ #ifndef O0ABSTRACTSTORE_H #define O0ABSTRACTSTORE_H #include #include #include "o0export.h" /// Storage for strings. class O0_EXPORT 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/core/libs/dplugins/webservices/o2/src/o0baseauth.cpp b/core/libs/dplugins/webservices/o2/src/o0baseauth.cpp index e7d385de43..054377159c 100644 --- a/core/libs/dplugins/webservices/o2/src/o0baseauth.cpp +++ b/core/libs/dplugins/webservices/o2/src/o0baseauth.cpp @@ -1,150 +1,160 @@ #include #include #include #include "o0baseauth.h" #include "o0globals.h" #include "o0settingsstore.h" #include "o2replyserver.h" static const quint16 DefaultLocalPort = 1965; O0BaseAuth::O0BaseAuth(QObject *parent, O0AbstractStore *store): QObject(parent), store_(0), useExternalWebInterceptor_(false), replyServer_(NULL) { localPort_ = DefaultLocalPort; setStore(store); } 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(); } bool O0BaseAuth::useExternalWebInterceptor() { return useExternalWebInterceptor_; } void O0BaseAuth::setUseExternalWebInterceptor(bool useExternalWebInterceptor) { useExternalWebInterceptor_ = useExternalWebInterceptor; } QByteArray O0BaseAuth::replyContent() const { - if(replyServer_ != NULL) { - return replyServer_->replyContent(); - } - - return QByteArray(); + return replyContent_; } void O0BaseAuth::setReplyContent(const QByteArray &value) { - if(replyServer_ != NULL) { - return replyServer_->setReplyContent(value); + replyContent_ = value; + if (replyServer_) { + replyServer_->setReplyContent(replyContent_); } } 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(); } +void O0BaseAuth::setReplyServer(O2ReplyServer * server) +{ + delete replyServer_; + + replyServer_ = server; + replyServer_->setReplyContent(replyContent_); +} + +O2ReplyServer * O0BaseAuth::replyServer() const +{ + return replyServer_; +} + 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/core/libs/dplugins/webservices/o2/src/o0baseauth.h b/core/libs/dplugins/webservices/o2/src/o0baseauth.h index 0cf887f00a..356c393098 100644 --- a/core/libs/dplugins/webservices/o2/src/o0baseauth.h +++ b/core/libs/dplugins/webservices/o2/src/o0baseauth.h @@ -1,136 +1,144 @@ #ifndef O0BASEAUTH_H #define O0BASEAUTH_H #include #include #include #include #include #include #include "o0export.h" #include "o0abstractstore.h" #include "o0requestparameter.h" class O2ReplyServer; /// Base class of OAuth authenticators class O0_EXPORT O0BaseAuth : public QObject { Q_OBJECT public: explicit O0BaseAuth(QObject *parent = 0, O0AbstractStore *store = 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); /// Should we use a reply server (default) or an external web interceptor? Q_PROPERTY(bool useExternalWebInterceptor READ useExternalWebInterceptor WRITE setUseExternalWebInterceptor) bool useExternalWebInterceptor(); void setUseExternalWebInterceptor(bool inUseExternalWebInterceptor); /// 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() const; void setReplyContent(const QByteArray &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); + /// Set local reply server + void setReplyServer(O2ReplyServer *server); + + O2ReplyServer * replyServer() const; + protected: QString clientId_; QString clientSecret_; QString redirectUri_; QString requestToken_; QString requestTokenSecret_; QUrl requestTokenUrl_; QUrl authorizeUrl_; QUrl accessTokenUrl_; quint16 localPort_; O0AbstractStore *store_; QVariantMap extraTokens_; bool useExternalWebInterceptor_; + QByteArray replyContent_; + +private: O2ReplyServer *replyServer_; }; #endif // O0BASEAUTH diff --git a/core/libs/dplugins/webservices/o2/src/o0globals.h b/core/libs/dplugins/webservices/o2/src/o0globals.h index f04a149c87..496b36dadb 100644 --- a/core/libs/dplugins/webservices/o2/src/o0globals.h +++ b/core/libs/dplugins/webservices/o2/src/o0globals.h @@ -1,62 +1,64 @@ #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"; const char O2_MIME_TYPE_JSON[] = "application/json"; // 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"; +const char O2_OAUTH2_STATE[] = "state"; // 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_HTTP_HEADER[] = "HTTP"; const char O2_HTTP_AUTHORIZATION_HEADER[] = "Authorization"; #endif // O0GLOBALS_H diff --git a/core/libs/dplugins/webservices/o2/src/o0simplecrypt.h b/core/libs/dplugins/webservices/o2/src/o0simplecrypt.h index 19b96fe19b..52ded62a0c 100644 --- a/core/libs/dplugins/webservices/o2/src/o0simplecrypt.h +++ b/core/libs/dplugins/webservices/o2/src/o0simplecrypt.h @@ -1,227 +1,232 @@ /* 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 - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) +#include +#endif #include "o0baseauth.h" /** @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 successful, 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 O0_EXPORT 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 integrity 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 occurred. */ 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 usable 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 usable 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 occurred, 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 occurred, 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 occurred, 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 occurred, 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; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + QRandomGenerator m_rand; +#endif }; Q_DECLARE_OPERATORS_FOR_FLAGS(O0SimpleCrypt::CryptoFlags) #endif // SimpleCrypt_H diff --git a/core/libs/dplugins/webservices/o2/src/o1.cpp b/core/libs/dplugins/webservices/o2/src/o1.cpp index 9c883a9baf..c7aa845dfa 100644 --- a/core/libs/dplugins/webservices/o2/src/o1.cpp +++ b/core/libs/dplugins/webservices/o2/src/o1.cpp @@ -1,421 +1,421 @@ #include #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #if QT_VERSION >= 0x050100 #include #endif #include "o1.h" #include "o2replyserver.h" #include "o0globals.h" #include "o0settingsstore.h" O1::O1(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) { setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); manager_ = manager ? manager : new QNetworkAccessManager(this); qRegisterMetaType("QNetworkReply::NetworkError"); setCallbackUrl(O2_CALLBACK_URL); } QByteArray O1::userAgent() const { return userAgent_; } void O1::setUserAgent(const QByteArray &v) { userAgent_ = v; } 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); std::sort(headers.begin(), headers.end()); 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); std::sort(headers.begin(), headers.end()); 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; } void O1::decorateRequest(QNetworkRequest &req, const QList &oauthParams) { req.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); if (!userAgent_.isEmpty()) { #if QT_VERSION >= 0x050000 req.setHeader(QNetworkRequest::UserAgentHeader, userAgent_); #else req.setRawHeader("User-Agent", userAgent_); #endif } } 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"; // Create the reply server if it doesn't exist // and we don't use an external web interceptor if(!useExternalWebInterceptor_) { - if(replyServer_ == NULL) { - replyServer_ = new O2ReplyServer(this); - connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + if(replyServer() == NULL) { + O2ReplyServer * replyServer = new O2ReplyServer(this); + connect(replyServer, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + setReplyServer(replyServer); } } if (linked()) { qDebug() << "O1::link: Linked already"; Q_EMIT linkingSucceeded(); return; } setLinked(false); setToken(""); setTokenSecret(""); setExtraTokens(QVariantMap()); - + if (!useExternalWebInterceptor_) { // Start reply server - if (!replyServer_->isListening()) - replyServer_->listen(QHostAddress::Any, localPort()); + if (!replyServer()->isListening()) + replyServer()->listen(QHostAddress::Any, localPort()); } - + // Get any query parameters for the request #if QT_VERSION >= 0x050000 QUrlQuery requestData; #else QUrl requestData = requestTokenUrl(); #endif O0RequestParameter param("", ""); foreach (param, requestParameters()) requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value))); // Get the request url and add parameters #if QT_VERSION >= 0x050000 QUrl requestUrl = requestTokenUrl(); requestUrl.setQuery(requestData); // Create request QNetworkRequest request(requestUrl); #else // Create request QNetworkRequest request(requestData); #endif // Create initial token request QList headers; headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).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 decorateRequest(request, 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()); qDebug() << QString( "Request: %1" ).arg(reply->request().url().toString()); 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(localPort()).toLatin1()); #else QUrlQuery query(url); query.addQueryItem(O2_OAUTH_TOKEN, requestToken_); query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).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 decorateRequest(request, 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(); - qWarning() << "data: " << QString(data); 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/core/libs/dplugins/webservices/o2/src/o1.h b/core/libs/dplugins/webservices/o2/src/o1.h index b54abd5a50..75302ad9f8 100644 --- a/core/libs/dplugins/webservices/o2/src/o1.h +++ b/core/libs/dplugins/webservices/o2/src/o1.h @@ -1,135 +1,135 @@ #ifndef O1_H #define O1_H #include #include #include #include "o0export.h" #include "o0baseauth.h" /// Simple OAuth 1.0 authenticator. class O0_EXPORT O1: public O0BaseAuth { Q_OBJECT public: /// HTTP User-Agent header /// Set user agent to a value unique for your application (https://tools.ietf.org/html/rfc7231#section-5.5.3) /// if you see the following error in the application log: /// O1::onTokenRequestError: 201 "Error transferring requestTokenUrl() - server replied: Forbidden" "Bad bot" Q_PROPERTY(QByteArray userAgent READ userAgent WRITE setUserAgent) QByteArray userAgent() const; void setUserAgent(const QByteArray &value); /// 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) + 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, QNetworkAccessManager *manager = 0, O0AbstractStore *store = 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); /// Add common configuration (headers) to @p req. void decorateRequest(QNetworkRequest &req, 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(); public Q_SLOTS: /// Handle verification received from the reply server. virtual void onVerificationReceived(QMap params); protected Q_SLOTS: /// 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(); QByteArray userAgent_; QUrl requestUrl_; QList requestParameters_; QString callbackUrl_; QUrl tokenUrl_; QUrl refreshTokenUrl_; QString verifier_; QString signatureMethod_; QNetworkAccessManager *manager_; }; #endif // O1_H diff --git a/core/libs/dplugins/webservices/o2/src/o1freshbooks.h b/core/libs/dplugins/webservices/o2/src/o1freshbooks.h old mode 100644 new mode 100755 diff --git a/core/libs/dplugins/webservices/o2/src/o1twitter.h b/core/libs/dplugins/webservices/o2/src/o1twitter.h index 1dedfc077c..de1a10e305 100644 --- a/core/libs/dplugins/webservices/o2/src/o1twitter.h +++ b/core/libs/dplugins/webservices/o2/src/o1twitter.h @@ -1,19 +1,19 @@ #ifndef O1TWITTER_H #define O1TWITTER_H #include "o0export.h" #include "o1.h" /// Twitter OAuth 1.0 client class O0_EXPORT O1Twitter: public O1 { Q_OBJECT public: explicit O1Twitter(QObject *parent = 0): O1(parent) { - setRequestTokenUrl(QUrl(QLatin1String("https://api.twitter.com/oauth/request_token"))); - setAuthorizeUrl(QUrl(QLatin1String("https://api.twitter.com/oauth/authenticate"))); - setAccessTokenUrl(QUrl(QLatin1String("https://api.twitter.com/oauth/access_token"))); + setRequestTokenUrl(QUrl("https://api.twitter.com/oauth/request_token")); + setAuthorizeUrl(QUrl("https://api.twitter.com/oauth/authenticate")); + setAccessTokenUrl(QUrl("https://api.twitter.com/oauth/access_token")); } }; #endif // O1TWITTER_H diff --git a/core/libs/dplugins/webservices/o2/src/o2.cpp b/core/libs/dplugins/webservices/o2/src/o2.cpp index 6a50a31b15..7a3096a0ec 100644 --- a/core/libs/dplugins/webservices/o2/src/o2.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2.cpp @@ -1,512 +1,523 @@ #include #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, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) { manager_ = manager ? manager : new QNetworkAccessManager(this); grantFlow_ = GrantFlowAuthorizationCode; localhostPolicy_ = QString(O2_CALLBACK_URL); qRegisterMetaType("QNetworkReply::NetworkError"); } 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(); } QVariantMap O2::extraRequestParams() { return extraReqParams_; } void O2::setExtraRequestParams(const QVariantMap &value) { extraReqParams_ = value; Q_EMIT extraRequestParamsChanged(); } 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"; // Create the reply server if it doesn't exist // and we don't use an external web interceptor if(!useExternalWebInterceptor_) { - if(replyServer_ == NULL) { - replyServer_ = new O2ReplyServer(this); - connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); - connect(replyServer_, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool))); + if(replyServer() == NULL) { + O2ReplyServer * replyServer = new O2ReplyServer(this); + connect(replyServer, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + connect(replyServer, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool))); + setReplyServer(replyServer); } } if (linked()) { qDebug() << "O2::link: Linked already"; Q_EMIT linkingSucceeded(); return; } setLinked(false); setToken(""); setTokenSecret(""); setExtraTokens(QVariantMap()); setRefreshToken(QString()); setExpires(0); if (grantFlow_ == GrantFlowAuthorizationCode || grantFlow_ == GrantFlowImplicit) { + QString uniqueState = QUuid::createUuid().toString().remove(QRegExp("([^a-zA-Z0-9]|[-])")); if (useExternalWebInterceptor_) { // Save redirect URI, as we have to reuse it when requesting the access token redirectUri_ = localhostPolicy_.arg(localPort()); } else { // Start listening to authentication replies - if (!replyServer_->isListening()) { - if (replyServer_->listen(QHostAddress::Any, localPort_)) { + if (!replyServer()->isListening()) { + if (replyServer()->listen(QHostAddress::Any, localPort_)) { qDebug() << "O2::link: Reply server listening on port" << localPort(); } else { qWarning() << "O2::link: Reply server failed to start listening on port" << localPort(); Q_EMIT linkingFailed(); return; } } - + // Save redirect URI, as we have to reuse it when requesting the access token - redirectUri_ = localhostPolicy_.arg(replyServer_->serverPort()); + redirectUri_ = localhostPolicy_.arg(replyServer()->serverPort()); + replyServer()->setUniqueState(uniqueState); } - + // Assemble initial 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_)); if ( !redirectUri_.isEmpty() ) parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_)); if ( !scope_.isEmpty() ) parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_.replace( " ", "+" ))); + parameters.append(qMakePair(QString(O2_OAUTH2_STATE), uniqueState)); if ( !apiKey_.isEmpty() ) parameters.append(qMakePair(QString(O2_OAUTH2_API_KEY), apiKey_)); foreach (QString key, extraRequestParams().keys()) { parameters.append(qMakePair(key, extraRequestParams().value(key).toString())); } // 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())); if ( !clientSecret_.isEmpty() ) 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())); if ( !apiKey_.isEmpty() ) parameters.append(O0RequestParameter(O2_OAUTH2_API_KEY, apiKey_.toUtf8())); foreach (QString key, extraRequestParams().keys()) { parameters.append(O0RequestParameter(key.toUtf8(), extraRequestParams().value(key).toByteArray())); } QByteArray payload = O0BaseAuth::createQueryParameters(parameters); qDebug() << "O2::link: Sending token request for resource owner flow"; 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); tokenRequest.setRawHeader("Accept", O2_MIME_TYPE_JSON); 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); qDebug() << QString("O2::onVerificationReceived: Exchange access code data:\n%1").arg(QString(data)); 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 if (grantFlow_ == GrantFlowImplicit) { // Check for mandatory tokens if (response.contains(O2_OAUTH2_ACCESS_TOKEN)) { qDebug() << "O2::onVerificationReceived: Access token returned for implicit flow"; setToken(response.value(O2_OAUTH2_ACCESS_TOKEN)); if (response.contains(O2_OAUTH2_EXPIRES_IN)) { bool ok = false; int expiresIn = response.value(O2_OAUTH2_EXPIRES_IN).toInt(&ok); if (ok) { qDebug() << "O2::onVerificationReceived: Token expires in" << expiresIn << "seconds"; setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn)); } } setLinked(true); Q_EMIT linkingSucceeded(); } else { qWarning() << "O2::onVerificationReceived: Access token missing from response for implicit flow"; Q_EMIT linkingFailed(); } } 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) { qDebug() << "O2::onTokenReplyFinished: reply is null"; return; } if (tokenReply->error() == QNetworkReply::NoError) { QByteArray replyData = tokenReply->readAll(); // Dump replyData // SENSITIVE DATA in RelWithDebInfo or Debug builds //qDebug() << "O2::onTokenReplyFinished: replyData\n"; //qDebug() << QString( replyData ); QVariantMap tokens = parseTokenResponse(replyData); // Dump tokens qDebug() << "O2::onTokenReplyFinished: Tokens returned:\n"; foreach (QString key, tokens.keys()) { // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first qDebug() << key << ": "<< tokens.value( key ).toString().left( 3 ) << "..."; } // Check for mandatory tokens if (tokens.contains(O2_OAUTH2_ACCESS_TOKEN)) { qDebug() << "O2::onTokenReplyFinished: Access token returned"; 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((int)(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: Access token missing from response"; 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(); + if (!tokenReply) + { + qDebug() << "O2::onTokenReplyError: reply is null"; + } else { + qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString(); + qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll(); + timedReplies_.remove(tokenReply); + } + 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()); - + if (refreshReply->error() == QNetworkReply::NoError) { QByteArray reply = refreshReply->readAll(); QVariantMap tokens = parseTokenResponse(reply); setToken(tokens.value(O2_OAUTH2_ACCESS_TOKEN).toString()); setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt())); QString refreshToken = tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString(); if(!refreshToken.isEmpty()) { setRefreshToken(refreshToken); } else { qDebug() << "No new refresh token. Keep the old one."; } timedReplies_.remove(refreshReply); setLinked(true); Q_EMIT linkingSucceeded(); Q_EMIT refreshFinished(QNetworkReply::NoError); qDebug() << " New token expires in" << expires() << "seconds"; } else { qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); } 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); } void O2::serverHasClosed(bool paramsfound) { if ( !paramsfound ) { // server has probably timed out after receiving first response Q_EMIT linkingFailed(); } } 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; } bool O2::ignoreSslErrors() { return timedReplies_.ignoreSslErrors(); } void O2::setIgnoreSslErrors(bool ignoreSslErrors) { timedReplies_.setIgnoreSslErrors(ignoreSslErrors); } diff --git a/core/libs/dplugins/webservices/o2/src/o2hubic.cpp b/core/libs/dplugins/webservices/o2/src/o2hubic.cpp index 5671cb9af1..e8213bf123 100644 --- a/core/libs/dplugins/webservices/o2/src/o2hubic.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2hubic.cpp @@ -1,17 +1,16 @@ #include "o2hubic.h" -#include "o2globals.h" #include "o2replyserver.h" #include static const char *HubicScope = "usage.r,account.r,getAllLinks.r,credentials.r,activate.w,links.drw"; static const char *HubicEndpoint = "https://api.hubic.com/oauth/auth/"; static const char *HubicTokenUrl = "https://api.hubic.com/oauth/token/"; static const char *HubicRefreshUrl = "https://api.hubic.com/oauth/token/"; O2Hubic::O2Hubic(QObject *parent): O2(parent) { setRequestUrl(HubicEndpoint); setTokenUrl(HubicTokenUrl); setRefreshTokenUrl(HubicRefreshUrl); setScope(HubicScope); setLocalhostPolicy("http://localhost:%1/"); } diff --git a/core/libs/dplugins/webservices/o2/src/o2msgraph.cpp b/core/libs/dplugins/webservices/o2/src/o2msgraph.cpp new file mode 100644 index 0000000000..2274a75a9f --- /dev/null +++ b/core/libs/dplugins/webservices/o2/src/o2msgraph.cpp @@ -0,0 +1,10 @@ +#include "o2msgraph.h" + +static const char *MsgraphEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; +static const char *MsgraphTokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + +O2Msgraph::O2Msgraph(QObject *parent): O2(parent) { + setRequestUrl(MsgraphEndpoint); + setTokenUrl(MsgraphTokenUrl); + setRefreshTokenUrl(MsgraphTokenUrl); +} diff --git a/core/libs/dplugins/webservices/o2/src/o2msgraph.h b/core/libs/dplugins/webservices/o2/src/o2msgraph.h new file mode 100644 index 0000000000..48db6c9f6b --- /dev/null +++ b/core/libs/dplugins/webservices/o2/src/o2msgraph.h @@ -0,0 +1,15 @@ +#ifndef O2MSGRAPH_H +#define O2MSGRAPH_H + +#include "o0export.h" +#include "o2.h" + +/// Microsoft Graph's dialect of OAuth 2.0 +class O0_EXPORT O2Msgraph: public O2 { + Q_OBJECT + +public: + explicit O2Msgraph(QObject *parent = 0); +}; + +#endif // O2MSGRAPH_H diff --git a/core/libs/dplugins/webservices/o2/src/o2replyserver.cpp b/core/libs/dplugins/webservices/o2/src/o2replyserver.cpp old mode 100644 new mode 100755 index fd268c5087..6fdfae2de4 --- a/core/libs/dplugins/webservices/o2/src/o2replyserver.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2replyserver.cpp @@ -1,168 +1,184 @@ #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif +#include "o0globals.h" #include "o2replyserver.h" O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent), timeout_(15), maxtries_(3), tries_(0) { qDebug() << "O2ReplyServer: Starting"; connect(this, SIGNAL(newConnection()), this, SLOT(onIncomingConnection())); replyContent_ = ""; } void O2ReplyServer::onIncomingConnection() { qDebug() << "O2ReplyServer::onIncomingConnection: Receiving..."; QTcpSocket *socket = nextPendingConnection(); connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); // Wait for a bit *after* first response, then close server if no usable data has arrived // Helps with implicit flow, where a URL fragment may need processed by local user-agent and // sent as secondary query string callback, or additional requests make it through first, // like for favicons, etc., before such secondary callbacks are fired QTimer *timer = new QTimer(socket); timer->setObjectName("timeoutTimer"); connect(timer, SIGNAL(timeout()), this, SLOT(closeServer())); timer->setSingleShot(true); timer->setInterval(timeout() * 1000); connect(socket, SIGNAL(readyRead()), timer, SLOT(start())); } void O2ReplyServer::onBytesReady() { if (!isListening()) { // server has been closed, stop processing queued connections return; } qDebug() << "O2ReplyServer::onBytesReady: Processing request"; // NOTE: on first call, the timeout timer is started QTcpSocket *socket = qobject_cast(sender()); if (!socket) { qWarning() << "O2ReplyServer::onBytesReady: No socket available"; 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()).toLatin1()); reply.append(replyContent_); socket->write(reply); qDebug() << "O2ReplyServer::onBytesReady: Sent reply"; QByteArray data = socket->readAll(); QMap queryParams = parseQueryParams(&data); if (queryParams.isEmpty()) { if (tries_ < maxtries_ ) { qDebug() << "O2ReplyServer::onBytesReady: No query params found, waiting for more callbacks"; ++tries_; return; } else { tries_ = 0; qWarning() << "O2ReplyServer::onBytesReady: No query params found, maximum callbacks received"; closeServer(socket, false); return; } } + if (!uniqueState_.isEmpty() && !queryParams.contains(QString(O2_OAUTH2_STATE))) { + qDebug() << "O2ReplyServer::onBytesReady: Malicious or service request"; + closeServer(socket, true); + return; // Malicious or service (e.g. favicon.ico) request + } qDebug() << "O2ReplyServer::onBytesReady: Query params found, closing server"; closeServer(socket, true); Q_EMIT verificationReceived(queryParams); } QMap O2ReplyServer::parseQueryParams(QByteArray *data) { qDebug() << "O2ReplyServer::parseQueryParams"; //qDebug() << QString("O2ReplyServer::parseQueryParams data:\n%1").arg(QString(*data)); 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; + QMap 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().toLatin1())); QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed().toLatin1())); queryParams.insert(key, value); } - return std::move(queryParams); + return queryParams; } void O2ReplyServer::closeServer(QTcpSocket *socket, bool hasparameters) { if (!isListening()) { return; } qDebug() << "O2ReplyServer::closeServer: Initiating"; int port = serverPort(); if (!socket && sender()) { QTimer *timer = qobject_cast(sender()); if (timer) { qWarning() << "O2ReplyServer::closeServer: Closing due to timeout"; timer->stop(); socket = qobject_cast(timer->parent()); timer->deleteLater(); } } if (socket) { QTimer *timer = socket->findChild("timeoutTimer"); if (timer) { qDebug() << "O2ReplyServer::closeServer: Stopping socket's timeout timer"; timer->stop(); } socket->disconnectFromHost(); } close(); qDebug() << "O2ReplyServer::closeServer: Closed, no longer listening on port" << port; Q_EMIT serverClosed(hasparameters); } QByteArray O2ReplyServer::replyContent() { return replyContent_; } void O2ReplyServer::setReplyContent(const QByteArray &value) { replyContent_ = value; } int O2ReplyServer::timeout() { return timeout_; } void O2ReplyServer::setTimeout(int timeout) { timeout_ = timeout; } int O2ReplyServer::callbackTries() { return maxtries_; } void O2ReplyServer::setCallbackTries(int maxtries) { maxtries_ = maxtries; } + +QString O2ReplyServer::uniqueState() +{ + return uniqueState_; +} + +void O2ReplyServer::setUniqueState(const QString &state) +{ + uniqueState_ = state; +} diff --git a/core/libs/dplugins/webservices/o2/src/o2replyserver.h b/core/libs/dplugins/webservices/o2/src/o2replyserver.h index 53c8923797..666b3309d7 100644 --- a/core/libs/dplugins/webservices/o2/src/o2replyserver.h +++ b/core/libs/dplugins/webservices/o2/src/o2replyserver.h @@ -1,50 +1,54 @@ #ifndef O2REPLYSERVER_H #define O2REPLYSERVER_H #include #include #include #include #include "o0export.h" /// HTTP server to process authentication response. class O0_EXPORT 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); /// Seconds to keep listening *after* first response for a callback with token content Q_PROPERTY(int timeout READ timeout WRITE setTimeout) int timeout(); void setTimeout(int timeout); /// Maximum number of callback tries to accept, in case some don't have token content (favicons, etc.) Q_PROPERTY(int callbackTries READ callbackTries WRITE setCallbackTries) int callbackTries(); void setCallbackTries(int maxtries); + QString uniqueState(); + void setUniqueState(const QString &state); + Q_SIGNALS: void verificationReceived(QMap); void serverClosed(bool); // whether it has found parameters public Q_SLOTS: void onIncomingConnection(); void onBytesReady(); QMap parseQueryParams(QByteArray *data); void closeServer(QTcpSocket *socket = 0, bool hasparameters = false); protected: QByteArray replyContent_; int timeout_; int maxtries_; int tries_; + QString uniqueState_; }; #endif // O2REPLYSERVER_H diff --git a/core/libs/dplugins/webservices/o2/src/o2requestor.cpp b/core/libs/dplugins/webservices/o2/src/o2requestor.cpp index 14fc32b1d5..ffbf2893ad 100644 --- a/core/libs/dplugins/webservices/o2/src/o2requestor.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2requestor.cpp @@ -1,209 +1,309 @@ +#include + #include #include +#include #if QT_VERSION >= 0x050000 #include #endif #include "o2requestor.h" #include "o2.h" #include "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), addAccessTokenInQuery_(true), rawData_(false) { 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() { } +void O2Requestor::setAddAccessTokenInQuery(bool value) { + addAccessTokenInQuery_ = value; +} + void O2Requestor::setAccessTokenInAuthenticationHTTPHeaderFormat(const QString &value) { accessTokenInAuthenticationHTTPHeaderFormat_ = value; } -int O2Requestor::get(const QNetworkRequest &req) { +int O2Requestor::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) { if (-1 == setup(req, QNetworkAccessManager::GetOperation)) { return -1; } reply_ = manager_->get(request_); - timedReplies_.add(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); 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) { +int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) { if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { return -1; } + rawData_ = true; data_ = data; reply_ = manager_->post(request_, data_); - timedReplies_.add(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); 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) { +int O2Requestor::post(const QNetworkRequest & req, QHttpMultiPart* data, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { + return -1; + } + rawData_ = false; + multipartData_ = data; + reply_ = manager_->post(request_, multipartData_); + multipartData_->setParent(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); + 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, int timeout/* = 60*1000*/) { if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { return -1; } + rawData_ = true; data_ = data; reply_ = manager_->put(request_, data_); - timedReplies_.add(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); 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, QHttpMultiPart* data, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { + return -1; + } + rawData_ = false; + multipartData_ = data; + reply_ = manager_->put(request_, multipartData_); + multipartData_->setParent(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); + 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::customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::CustomOperation, verb)) { + return -1; + } + data_ = data; + QBuffer * buffer = new QBuffer; + buffer->setData(data_); + reply_ = manager_->sendCustomRequest(request_, verb, buffer); + buffer->setParent(reply_); + timedReplies_.add(new O2Reply(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::head(const QNetworkRequest &req, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::HeadOperation)) { + return -1; + } + reply_ = manager_->head(request_); + timedReplies_.add(new O2Reply(reply_, timeout)); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + 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) { + if (reply_ != qobject_cast(sender())) { return; } - if (error == QNetworkReply::NoError) { + if (reply_->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(); 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; } + // Restart timeout because request in progress + O2Reply *o2Reply = timedReplies_.find(reply_); + if(o2Reply) + o2Reply->start(); Q_EMIT uploadProgress(id_, uploaded, total); } -int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation) { +int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) { 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(); + url_ = req.url(); + + QUrl url = url_; + if (addAccessTokenInQuery_) { #if QT_VERSION < 0x050000 - url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); #else - QUrlQuery query(url); - query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); - url.setQuery(query); + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); #endif + } + request_.setUrl(url); - + // If the service require the access token to be sent as a Authentication HTTP header, we add the access token. - if(!accessTokenInAuthenticationHTTPHeaderFormat_.isEmpty()) { + if (!accessTokenInAuthenticationHTTPHeaderFormat_.isEmpty()) { request_.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, accessTokenInAuthenticationHTTPHeaderFormat_.arg(authenticator_->token()).toLatin1()); } - + + if (!verb.isEmpty()) { + request_.setRawHeader(O2_HTTP_HTTP_HEADER, verb); + } + 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(); + QList headers = reply_->rawHeaderPairs(); Q_EMIT finished(id_, error_, data); + Q_EMIT finished(id_, error_, data, headers); } 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 (addAccessTokenInQuery_) { #if QT_VERSION < 0x050000 - url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); #else - QUrlQuery query(url); - query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); - url.setQuery(query); + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); #endif + } request_.setUrl(url); - + // If the service require the access token to be sent as a Authentication HTTP header, // we update the access token when retrying. if(!accessTokenInAuthenticationHTTPHeaderFormat_.isEmpty()) { request_.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, accessTokenInAuthenticationHTTPHeaderFormat_.arg(authenticator_->token()).toLatin1()); } - + status_ = ReRequesting; switch (operation_) { case QNetworkAccessManager::GetOperation: reply_ = manager_->get(request_); break; case QNetworkAccessManager::PostOperation: - reply_ = manager_->post(request_, data_); + reply_ = rawData_ ? manager_->post(request_, data_) : manager_->post(request_, multipartData_); + break; + case QNetworkAccessManager::CustomOperation: + { + QBuffer * buffer = new QBuffer; + buffer->setData(data_); + reply_ = manager_->sendCustomRequest(request_, request_.rawHeader(O2_HTTP_HTTP_HEADER), buffer); + buffer->setParent(reply_); + } + break; + case QNetworkAccessManager::PutOperation: + reply_ = rawData_ ? manager_->post(request_, data_) : manager_->put(request_, multipartData_); + break; + case QNetworkAccessManager::HeadOperation: + reply_ = manager_->head(request_); break; default: - reply_ = manager_->put(request_, data_); + assert(!"Unspecified operation for request"); + reply_ = manager_->get(request_); + break; } 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/core/libs/dplugins/webservices/o2/src/o2requestor.h b/core/libs/dplugins/webservices/o2/src/o2requestor.h index 730f2f05d9..a157bbe9ad 100644 --- a/core/libs/dplugins/webservices/o2/src/o2requestor.h +++ b/core/libs/dplugins/webservices/o2/src/o2requestor.h @@ -1,90 +1,120 @@ #ifndef O2REQUESTOR_H #define O2REQUESTOR_H #include #include #include #include #include #include +#include #include "o0export.h" #include "o2reply.h" class O2; /// Makes authenticated requests. class O0_EXPORT O2Requestor: public QObject { Q_OBJECT public: explicit O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent = 0); ~O2Requestor(); - + + + /// Some services require the access token to be sent as a Authentication HTTP header + /// and refuse requests with the access token in the query. + /// This function allows to use or ignore the access token in the query. + /// The default value of `true` means that the query will contain the access token. + /// By setting the value to false, the query will not contain the access token. + /// See: + /// https://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16#section-4.3 + /// https://tools.ietf.org/html/rfc6750#section-2.3 + + void setAddAccessTokenInQuery(bool value); + /// Some services require the access token to be sent as a Authentication HTTP header. /// This is the case for Twitch and Mixer. /// When the access token expires and is refreshed, O2Requestor::retry() needs to update the Authentication HTTP header. /// In order to do so, O2Requestor needs to know the format of the Authentication HTTP header. void setAccessTokenInAuthenticationHTTPHeaderFormat(const QString &value); 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); + int get(const QNetworkRequest &req, int timeout = 60*1000); /// 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); + int post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000); + int post(const QNetworkRequest &req, QHttpMultiPart* data, int timeout = 60*1000); /// 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); + int put(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000); + int put(const QNetworkRequest &req, QHttpMultiPart* data, int timeout = 60*1000); + + /// Make a HEAD request. + /// @return Request ID or -1 if there are too many requests in the queue. + int head(const QNetworkRequest &req, int timeout = 60*1000); + + /// Make a custom request. + /// @return Request ID or -1 if there are too many requests in the queue. + int customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, int timeout = 60*1000); Q_SIGNALS: + /// Emitted when a request has been completed or failed. void finished(int id, QNetworkReply::NetworkError error, QByteArray data); + /// Emitted when a request has been completed or faled. Also reply headers will be provided. + void finished(int id, QNetworkReply::NetworkError error, QByteArray data, QList headers); + /// 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); + int setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray()); enum Status { Idle, Requesting, ReRequesting }; QNetworkAccessManager *manager_; O2 *authenticator_; QNetworkRequest request_; QByteArray data_; + QHttpMultiPart* multipartData_; QNetworkReply *reply_; Status status_; int id_; QNetworkAccessManager::Operation operation_; QUrl url_; O2ReplyList timedReplies_; QNetworkReply::NetworkError error_; + bool addAccessTokenInQuery_; QString accessTokenInAuthenticationHTTPHeaderFormat_; + bool rawData_; }; #endif // O2REQUESTOR_H diff --git a/core/libs/dplugins/webservices/o2/src/o2simplecrypt.cpp b/core/libs/dplugins/webservices/o2/src/o2simplecrypt.cpp index 210ae12b92..ffe9d9d9ad 100644 --- a/core/libs/dplugins/webservices/o2/src/o2simplecrypt.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2simplecrypt.cpp @@ -1,254 +1,266 @@ /* 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 "o0simplecrypt.h" #include #include #include #include #include #include O0SimpleCrypt::O0SimpleCrypt(): m_key(0), m_compressionMode(CompressionAuto), m_protectionMode(ProtectionChecksum), m_lastError(ErrorNoError) { +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#else + m_rand.seed(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#endif } O0SimpleCrypt::O0SimpleCrypt(quint64 key): m_key(key), m_compressionMode(CompressionAuto), m_protectionMode(ProtectionChecksum), m_lastError(ErrorNoError) { +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#else + m_rand.seed(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#endif 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 +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) char randomChar = char(qrand() & 0xFF); +#else + char randomChar = char(m_rand.generate() & 0xFF); +#endif 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/core/libs/dplugins/webservices/o2/src/o2surveymonkey.cpp b/core/libs/dplugins/webservices/o2/src/o2surveymonkey.cpp index 8eb3613a0c..0f1b4a297d 100644 --- a/core/libs/dplugins/webservices/o2/src/o2surveymonkey.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2surveymonkey.cpp @@ -1,21 +1,20 @@ #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include "o2surveymonkey.h" -#include "o2globals.h" static const char *SMEndpoint = "https://api.surveymonkey.net/oauth/authorize"; static const char *SMTokenUrl = "https://api.surveymonkey.net/oauth/token"; static const quint16 SMLocalPort = 8000; O2SurveyMonkey::O2SurveyMonkey(QObject *parent): O2(parent) { setRequestUrl(SMEndpoint); setTokenUrl(SMTokenUrl); setLocalPort(SMLocalPort); setIgnoreSslErrors(true); //needed on Mac } diff --git a/core/libs/dplugins/webservices/o2/src/o2uber.cpp b/core/libs/dplugins/webservices/o2/src/o2uber.cpp index 9bb5d6e13d..632c84e1f2 100644 --- a/core/libs/dplugins/webservices/o2/src/o2uber.cpp +++ b/core/libs/dplugins/webservices/o2/src/o2uber.cpp @@ -1,98 +1,99 @@ #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include "o2uber.h" #include "o0globals.h" static const char *UberEndpoint = "https://login.uber.com/oauth/v2/authorize"; static const char *UberTokenUrl = "https://login.uber.com/oauth/v2/token"; static const char *UberExpiresIn = "expires_in"; static const char *UberGrantType = "authorization_code"; O2Uber::O2Uber(QObject *parent): O2(parent) { setRequestUrl(UberEndpoint); setTokenUrl(UberTokenUrl); } void O2Uber::onVerificationReceived(const QMap response){ qDebug() << "O2Uber::onVerificationReceived: Emitting closeBrowser()"; Q_EMIT closeBrowser(); if (response.contains("error")) { qWarning() << "O2Uber::onVerificationReceived: Verification failed"; foreach (QString key, response.keys()) { qWarning() << "O2Uber::onVerificationReceived:" << key << response.value(key); } Q_EMIT linkingFailed(); return; } // Save access code setCode(response.value(O2_OAUTH2_GRANT_TYPE_CODE)); // Exchange access code for access/refresh tokens QUrl url(tokenUrl_); #if QT_VERSION < 0x050000 url.addQueryItem(O2_OAUTH2_CLIENT_ID, clientId_); url.addQueryItem(O2_OAUTH2_CLIENT_SECRET, clientSecret_); url.addQueryItem(O2_OAUTH2_GRANT_TYPE, UberGrantType); url.addQueryItem(O2_OAUTH2_REDIRECT_URI, redirectUri_); url.addQueryItem(O2_OAUTH2_GRANT_TYPE_CODE, code()); url.addQueryItem(O2_OAUTH2_SCOPE, scope_); #else QUrlQuery query(url); query.addQueryItem(O2_OAUTH2_CLIENT_ID, clientId_); query.addQueryItem(O2_OAUTH2_CLIENT_SECRET, clientSecret_); query.addQueryItem(O2_OAUTH2_GRANT_TYPE, UberGrantType); query.addQueryItem(O2_OAUTH2_REDIRECT_URI, redirectUri_); query.addQueryItem(O2_OAUTH2_GRANT_TYPE_CODE, code()); query.addQueryItem(O2_OAUTH2_SCOPE, scope_); url.setQuery(query); #endif QNetworkRequest tokenRequest(url); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *tokenReply = manager_->post(tokenRequest, QByteArray()); 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); } void O2Uber::onTokenReplyFinished(){ qDebug() << "O2Uber::onTokenReplyFinished"; QNetworkReply *tokenReply = qobject_cast(sender()); if (tokenReply->error() == QNetworkReply::NoError) { // Process reply QByteArray replyData = tokenReply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(replyData); const QJsonObject rootObject = doc.object(); QVariantMap reply; for (const QString &key : rootObject.keys()) { reply.insert(key, rootObject[key].toVariant()); } // Interpret reply setToken(reply.value(O2_OAUTH2_ACCESS_TOKEN, QString()).toString()); setExpires(reply.value(UberExpiresIn).toInt()); setRefreshToken(reply.value(O2_OAUTH2_REFRESH_TOKEN, QString()).toString()); setExtraTokens(reply); timedReplies_.remove(tokenReply); setLinked(true); Q_EMIT linkingSucceeded(); } else { qWarning() << "O2Uber::onTokenReplyFinished:" << tokenReply->errorString(); } } diff --git a/core/libs/dplugins/webservices/o2/src/o2vimeo.cpp b/core/libs/dplugins/webservices/o2/src/o2vimeo.cpp new file mode 100644 index 0000000000..806b3f0d94 --- /dev/null +++ b/core/libs/dplugins/webservices/o2/src/o2vimeo.cpp @@ -0,0 +1,13 @@ +#include "o2vimeo.h" + +// Vimeo supported scopes: https://developer.vimeo.com/api/authentication#supported-scopes +static const char *VimeoScope = "public"; +static const char *VimeoEndpoint = "https://api.vimeo.com/oauth/authorize"; +static const char *VimeoTokenUrl = "https://api.vimeo.com/oauth/access_token"; + +O2Vimeo::O2Vimeo(QObject *parent): O2(parent) { + setRequestUrl(VimeoEndpoint); + setTokenUrl(VimeoTokenUrl); + setRefreshTokenUrl(VimeoTokenUrl); + setScope(VimeoScope); +} diff --git a/core/libs/dplugins/webservices/o2/src/o2vimeo.h b/core/libs/dplugins/webservices/o2/src/o2vimeo.h new file mode 100644 index 0000000000..f1acd164ff --- /dev/null +++ b/core/libs/dplugins/webservices/o2/src/o2vimeo.h @@ -0,0 +1,15 @@ +#ifndef O2VIMEO_H +#define O2VIMEO_H + +#include "o0export.h" +#include "o2.h" + +/// Vimeo dialect of OAuth 2.0 +class O0_EXPORT O2Vimeo : public O2 { + Q_OBJECT + +public: + explicit O2Vimeo(QObject *parent = 0); +}; + +#endif // O2VIMEO_H