diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt new file mode 100644 index 00000000..40cefc55 --- /dev/null +++ b/3rdparty/CMakeLists.txt @@ -0,0 +1 @@ +ADD_SUBDIRECTORY(o2) diff --git a/3rdparty/o2/.gitignore b/3rdparty/o2/.gitignore new file mode 100644 index 00000000..f6640596 --- /dev/null +++ b/3rdparty/o2/.gitignore @@ -0,0 +1,2 @@ +*CMakeLists.txt.user +_build/ diff --git a/3rdparty/o2/CMakeLists.txt b/3rdparty/o2/CMakeLists.txt new file mode 100644 index 00000000..b5c22538 --- /dev/null +++ b/3rdparty/o2/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.11) + +add_subdirectory(src) + +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif(BUILD_EXAMPLES) diff --git a/3rdparty/o2/LICENSE b/3rdparty/o2/LICENSE new file mode 100644 index 00000000..2984e7af --- /dev/null +++ b/3rdparty/o2/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2012, Akos Polster +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/o2/README.md b/3rdparty/o2/README.md new file mode 100644 index 00000000..6b255641 --- /dev/null +++ b/3rdparty/o2/README.md @@ -0,0 +1,192 @@ +# OAuth 1.0 and 2.0 for Qt + +This library encapsulates the OAuth 1.0 and 2.0 client authentication flows, and the sending of authenticated HTTP requests. + +The primary target is Qt Quick applications on mobile devices. + +## Contents + +Class | Header | Purpose +:-- | :-- | :-- +O1 | o1.h | Generic OAuth 1.0 authentication +O1RequestParameter | o1.h | An extra request parameter participating in request signing +O1Dropbox | o1dropbox.h | Dropbox OAuth specializations +O1Flickr | o1flickr.h | Flickr OAuth specializations +O1Requestor | o1requestor.h | Makes authenticated OAuth 1.0 requests: GET, POST or PUT, handles timeouts +O1Twitter | o1twitter.h | Twitter OAuth specializations +OXTwitter | oxtwitter.h | Twitter XAuth specialization +O2 | o2.h | Generic OAuth 2.0 authentication +O2Facebook | o2facebook.h | Facebook OAuth specialization +O2Gft | o2gft.h | Google Fusion Tables OAuth specialization +O2Reply | o2reply.h | A network request/reply that can time out +O2ReplyServer | o2replyserver.h | HTTP server to process authentication responses +O2Requestor | o2requestor.h | Makes authenticated OAuth 2.0 requests (GET, POST or PUT), handles timeouts and token expiry +O2Skydrive | o2skydrive.h | Skydrive OAuth specialization +SimpleCrypt | simplecrypt.h | Simple encryption and decryption by Andre Somers +O2AbstractStore | o2abstractstore.h | Base class for implemnting persistent stores +O2SettingsStore | o2settingsstore.h | A QSettings based persistent store for writing OAuth tokens + +## Installation + +Clone the Github repository, then add all files to your Qt project. + +## Basic Usage + +This example assumes a hypothetical Twitter client that will post tweets. Twitter is using OAuth 1.0. + +### Setup + +Include the required header files, and have some member variables that will be used for authentication and sending requests: + + #include "o1twitter.h" + #include "o1requestor.h" + O1Twitter *o1; + +### Initialization + +Instantiate one of the authenticator classes, like O1Twitter, set your application ID and application secret, and install the signal handlers: + + o1 = new O1Twitter(this); + o1->setClientId(MY_CLIENT_ID); + o1->setClientSecret(MY_CLIENT_SECRET); + connect(o1, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o1, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(o1, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o1, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o1, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); + +**Note:** For browserless Twitter authentication, you can use the OXTwitter specialized class which can do Twitter XAuth. You will need to additionally provide your Twitter login credentials (username & password) before calling *link()*. + +### Handling Signals + +O2 is an asynchronous library. It will send signals at various stages of authentication and request processing. + +To handle these signals, implement the following slots in your code: + + void onLinkedChanged() { + // Linking (login) state has changed. + // Use o1->linked() to get the actual state + } + + void onLinkingFailed() { + // Login has failed + } + + void onLinkingSucceeded() { + // Login has succeeded + } + + void onOpenBrowser(const QUrl *url) { + // Open a web browser or a web view with the given URL. + // The user will interact with this browser window to + // enter login name, password, and authorize your application + // to access the Twitter account + } + + void onCloseBrowser() { + // Close the browser window opened in openBrowser() + } + +### Logging In + +To log in (or, to be more accurate, to link your application to the OAuth service), call the link() method: + + o1->link(); + +This initiates the authentication sequence. Your signal handlers above will be called at various stages. Lastly, if linking succeeds, onLinkingSucceeded() will be called. + +### Logging Out + +To log out, call the unlink() method: + + o1->unlink(); + +Logging out always succeeds, and requires no user interaction. + +### Sending Authenticated Requests + +Once linked, you can start sending authenticated requests to the service. We start with a simple example of sending a text-only tweet or as it's known in Twitter docs, a 'status update'. + +First we need a Qt network manager and an O1 requestor object: + + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + O1Requestor* requestor = new O1Requestor(manager, o1, this); + +Next, create parameters for posting the update: + + QByteArray paramName("status"); + QByteArray tweetText("My first tweet!"); + + QList reqParams = QList(); + reqParams << O1RequestParameter(paramName, tweetText); + + QByteArray postData = O1::createQueryParams(reqParams); + + // Using Twitter's REST API ver 1.1 + QUrl url = QUrl("https://api.twitter.com/1.1/statuses/update.json"); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + +Finally we authenticate and send the request using the O1 requestor object: + + QNetworkReply *reply = requestor->post(request, reqParams, postData); + +Continuing with the example, we will now send a tweet containing an image as well as a message. + +We create an HTTP request containing the image and the message, in the format specified by Twitter: + + QString imagePath("/tmp/image.jpg"); + QString message("My tweet with an image!"); + + QFileInfo fileInfo(imagePath); + QFile file(imagePath); + file.open(QIODevice::ReadOnly); + + QString boundary("7d44e178b0439"); + QByteArray data(QString("--" + boundary + "\r\n").toAscii()); + data += "Content-Disposition: form-data; name=\"media[]\"; filename=\"" + fileInfo.fileName() + "\"\r\n"; + data += "Content-Transfer-Encoding: binary\r\n"; + data += "Content-Type: application/octet-stream\r\n\r\n"; + data += file.readAll(); + file.close(); + data += QString("\r\n--") + boundary + "\r\n"; + data += "Content-Disposition: form-data; name=\"status\"\r\n"; + data += "Content-Transfer-Encoding: binary\r\n"; + data += "Content-Type: text/plain; charset=utf-8\r\n\r\n"; + data += message.toUtf8(); + data += QString("\r\n--") + boundary + "--\r\n"; + + QNetworkRequest request; + // Using Twitter's REST API ver 1.1 + static const char *uploadUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json"; + request.setUrl(QUrl(uploadUrl)); + request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); + request.setHeader(QNetworkRequest::ContentLengthHeader, data.length()); + + QNetworkReply *reply = requestor->post(request, QList(), data); + +That's it. Tweets using the O2 library! + +### Storing OAuth Tokens + +O2 provides simple storage classes for writing OAuth tokens in a peristent location. Currently, a QSettings based backing store **O2SettingsStore** is provided in O2. O2SettingsStore keeps all token values in an encrypted form. You have to specify the encryption key to use while constructing the object: + + O2SettingsStore settings = new O2SettingsStore("myencryptionkey"); + // Set the store before starting OAuth, i.e before calling link() + o1->setStore(settings); + ... + +You can also create it with your customized QSettings object. O2SettingsStore will then use that QSettings object for storing the tokens: + + O2SettingsStore settings = new O2SettingsStore(mySettingsObject, "myencryptionkey"); + +Once set, O2SettingsStore takes ownership of the QSettings object. + +**Note:** If you do not specify a storage object to use, O2 will create one by default (which QSettings based), and use it. In such a case, a default encryption key is used for encrypting the data. + +### Extra OAuth Tokens +Some OAuth service providers provide additional information in the access token response. Eg: Twitter returns 2 additional tokens in it's access token response - *screen_name* and *user_id*. + +O2 provides all such tokens via the property - *extraTokens*. You can query this property after a successful OAuth exchange, i.e after the *linkingSucceeded()* signal has been emitted. + diff --git a/3rdparty/o2/acknowledgements.md b/3rdparty/o2/acknowledgements.md new file mode 100644 index 00000000..e28e7169 --- /dev/null +++ b/3rdparty/o2/acknowledgements.md @@ -0,0 +1,37 @@ +# SimpleCrypt + +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. + +# Contributions by Mandeep Sandhu + +"Hi Akos, + +I'm writing this mail to confirm that my contributions to the O2 library, available here https://github.com/pipacs/o2, can be freely distributed according to the project's license (as shown in the LICENSE file). + +Regards, +-mandeep" + diff --git a/3rdparty/o2/examples/.gitignore b/3rdparty/o2/examples/.gitignore new file mode 100644 index 00000000..da1c502e --- /dev/null +++ b/3rdparty/o2/examples/.gitignore @@ -0,0 +1,6 @@ +*.pro.user +build* +*.o +*.moc +moc_* +Makefile diff --git a/3rdparty/o2/examples/CMakeLists.txt b/3rdparty/o2/examples/CMakeLists.txt new file mode 100644 index 00000000..e19c1470 --- /dev/null +++ b/3rdparty/o2/examples/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.11) + +if(WITH_FACEBOOK) + add_subdirectory(facebookdemo) +endif(WITH_FACEBOOK) + +if(WITH_TWITTER) + add_subdirectory(twitterdemo) +endif(WITH_TWITTER) diff --git a/3rdparty/o2/examples/facebookdemo/CMakeLists.txt b/3rdparty/o2/examples/facebookdemo/CMakeLists.txt new file mode 100644 index 00000000..64b13282 --- /dev/null +++ b/3rdparty/o2/examples/facebookdemo/CMakeLists.txt @@ -0,0 +1,25 @@ +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") + +set(QT_USE_QTNETWORK true) +set(QT_USE_QTSCRIPT true) + +find_package(Qt4 REQUIRED) + +include( ${QT_USE_FILE} ) +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) + +set(fb_SRCS + main.cpp + fbdemo.cpp +) + +add_definitions(${QT4_DEFINITIONS}) +add_executable( fbexample ${fb_SRCS} ) + +target_link_libraries( fbexample ${QT_LIBRARIES} o2 ) diff --git a/3rdparty/o2/examples/facebookdemo/facebookdemo.pro b/3rdparty/o2/examples/facebookdemo/facebookdemo.pro new file mode 100644 index 00000000..31f94371 --- /dev/null +++ b/3rdparty/o2/examples/facebookdemo/facebookdemo.pro @@ -0,0 +1,16 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +include(../../src/src.pri) + +TARGET = facebookdemo +TEMPLATE = app + +SOURCES += main.cpp \ + fbdemo.cpp + +HEADERS += \ + fbdemo.h diff --git a/3rdparty/o2/examples/facebookdemo/fbdemo.cpp b/3rdparty/o2/examples/facebookdemo/fbdemo.cpp new file mode 100644 index 00000000..43455362 --- /dev/null +++ b/3rdparty/o2/examples/facebookdemo/fbdemo.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +#include "fbdemo.h" +#include "o2globals.h" +#include "o2settingsstore.h" + +const char FB_APP_KEY[] = "227896037359072"; +const char FB_APP_SECRET[] = "3d35b063872579cf7213e09e76b65ceb"; + +const char FB_REQUEST_URL[] = "https://www.facebook.com/dialog/oauth"; +const char FB_TOKEN_URL[] = "https://graph.facebook.com/oauth/access_token"; +const char FB_DEBUG_TOKEN[] = "https://graph.facebook.com/me?fields=id&access_token=%1"; + +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)) + +FBDemo::FBDemo(QObject *parent) : + QObject(parent) { + o2Facebook_ = new O2Facebook(this); + + o2Facebook_->setClientId(FB_APP_KEY); + o2Facebook_->setClientSecret(FB_APP_SECRET); + o2Facebook_->setLocalPort(localPort); + o2Facebook_->setRequestUrl(FB_REQUEST_URL); + o2Facebook_->setTokenUrl(FB_TOKEN_URL); + + // Create a store object for writing the received tokens + O2SettingsStore *store = new O2SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("facebook"); + o2Facebook_->setStore(store); + + connect(o2Facebook_, SIGNAL(linkedChanged()), + this, SLOT(onLinkedChanged())); + connect(o2Facebook_, SIGNAL(linkingFailed()), + this, SIGNAL(linkingFailed())); + connect(o2Facebook_, SIGNAL(linkingSucceeded()), + this, SLOT(onLinkingSucceeded())); + connect(o2Facebook_, SIGNAL(openBrowser(QUrl)), + this, SLOT(onOpenBrowser(QUrl))); + connect(o2Facebook_, SIGNAL(closeBrowser()), + this, SLOT(onCloseBrowser())); +} + +void FBDemo::doOAuth(O2::GrantFlow grantFlowType) { + qDebug() << "Starting OAuth 2 with grant flow type" + << GRANTFLOW_STR(grantFlowType) << "..."; + o2Facebook_->setGrantFlow(grantFlowType); + o2Facebook_->link(); +} + +void FBDemo::validateToken() { + if (!o2Facebook_->linked()) { + qWarning() << "ERROR: Application is not linked!"; + emit linkingFailed(); + return; + } + QString accessToken = o2Facebook_->token(); + QString debugUrlStr = QString(FB_DEBUG_TOKEN).arg(accessToken); + + QNetworkRequest request = QNetworkRequest(QUrl(debugUrlStr)); + QNetworkAccessManager *mgr = new QNetworkAccessManager(this); + QNetworkReply *reply = mgr->get(request); + connect(reply, SIGNAL(finished()), this, SLOT(onFinished())); + qDebug() << "Validating user token. Please wait..."; +} + +void FBDemo::onOpenBrowser(const QUrl &url) { + QDesktopServices::openUrl(url); +} + +void FBDemo::onCloseBrowser() { +} + +void FBDemo::onLinkedChanged() { + qDebug() << "Link changed!"; +} + +void FBDemo::onLinkingSucceeded() { + O2Facebook* o1t = qobject_cast(sender()); + + QVariantMap extraTokens = o1t->extraTokens(); + + if (!extraTokens.isEmpty()) { + emit extraTokensReady(extraTokens); + + qDebug() << "Extra tokens in response:"; + foreach (QString key, extraTokens.keys()) { + qDebug() << "\t" << key << ":" << extraTokens.value(key).toString(); + } + } + emit linkingSucceeded(); +} + +void FBDemo::onFinished() { + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) { + qWarning() << "NULL reply!"; + emit linkingFailed(); + return; + } + + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "Reply error:" << reply->error(); + qWarning() << "Reason:" << reply->errorString(); + emit linkingFailed(); + return; + } + + QByteArray replyData = reply->readAll(); + bool valid = !replyData.contains("error"); + + if (valid) { + qDebug() << "Token is valid"; + emit linkingSucceeded(); + } else { + qDebug() << "Token is invalid"; + emit linkingFailed(); + } +} diff --git a/3rdparty/o2/examples/facebookdemo/fbdemo.h b/3rdparty/o2/examples/facebookdemo/fbdemo.h new file mode 100644 index 00000000..054e0060 --- /dev/null +++ b/3rdparty/o2/examples/facebookdemo/fbdemo.h @@ -0,0 +1,35 @@ +#ifndef FBDEMO_H +#define FBDEMO_H + +#include + +#include "o2facebook.h" + +class FBDemo : public QObject +{ + Q_OBJECT + +public: + explicit FBDemo(QObject *parent = 0); + +signals: + void extraTokensReady(const QVariantMap &extraTokens); + void linkingFailed(); + void linkingSucceeded(); + +public slots: + void doOAuth(O2::GrantFlow grantFlowType); + void validateToken(); + +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + void onCloseBrowser(); + void onFinished(); + +private: + O2Facebook *o2Facebook_; +}; + +#endif // FBDEMO_H diff --git a/3rdparty/o2/examples/facebookdemo/main.cpp b/3rdparty/o2/examples/facebookdemo/main.cpp new file mode 100644 index 00000000..2ba3d915 --- /dev/null +++ b/3rdparty/o2/examples/facebookdemo/main.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#include "fbdemo.h" + +const char OPT_OAUTH_CODE[] = "-o"; +const char OPT_VALIDATE_TOKEN[] = "-v"; + +const char USAGE[] = "\n" + "Usage: facebookdemo [OPTION]...\n" + "Get OAuth2 access tokens from Facebook's OAuth service\n" + "\nOptions:\n" + " %1\t\tLink with Facebook OAuth2 service using Authorization Code\n" + " %2\t\tValidate Access Token\n"; + + +class Helper : public QObject +{ + Q_OBJECT + +public: + Helper() : QObject(), fbdemo_(this), waitForMsg_(false), msg_(QString()) {} + +public slots: + + void processArgs() { + + QStringList argList = qApp->arguments(); + + QByteArray help = QString(USAGE).arg(OPT_OAUTH_CODE, + OPT_VALIDATE_TOKEN).toLatin1(); + + const char* helpText = help.constData(); + + connect(&fbdemo_, SIGNAL(linkingFailed()), + this, SLOT(onLinkingFailed())); + connect(&fbdemo_, SIGNAL(linkingSucceeded()), + this, SLOT(onLinkingSucceeded())); + + if (argList.contains(OPT_OAUTH_CODE)) { + // Start OAuth + fbdemo_.doOAuth(O2::GrantFlowAuthorizationCode); + } else if (argList.contains(OPT_VALIDATE_TOKEN)) { + fbdemo_.validateToken(); + } else { + qDebug() << helpText; + qApp->exit(1); + } + } + + void onLinkingFailed() { + qDebug() << "Linking failed!"; + qApp->exit(1); + } + + void onLinkingSucceeded() { + qDebug() << "Linking succeeded!"; + if (waitForMsg_) { + //postStatusUpdate(msg_); + } else { + qApp->quit(); + } + } + +private: + FBDemo fbdemo_; + bool waitForMsg_; + QString msg_; +}; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + Helper helper; + QTimer::singleShot(0, &helper, SLOT(processArgs())); + + return a.exec(); +} + +#include "main.moc" diff --git a/3rdparty/o2/examples/twitterdemo/CMakeLists.txt b/3rdparty/o2/examples/twitterdemo/CMakeLists.txt new file mode 100644 index 00000000..e1ee3a50 --- /dev/null +++ b/3rdparty/o2/examples/twitterdemo/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.8.11) + +project( twitterexample ) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + +set(QT_USE_QTNETWORK true) +set(QT_USE_QTSCRIPT true) + +find_package(Qt4 REQUIRED) + +include( ${QT_USE_FILE} ) +include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} "../../src" ) + +set(fb_SRCS + main.cpp + tweeter.cpp +) + +add_definitions(${QT4_DEFINITIONS}) +add_executable( twitterexample ${fb_SRCS} ) + +target_link_libraries( twitterexample ${QT_LIBRARIES} o2 ) diff --git a/3rdparty/o2/examples/twitterdemo/main.cpp b/3rdparty/o2/examples/twitterdemo/main.cpp new file mode 100644 index 00000000..22ecc02b --- /dev/null +++ b/3rdparty/o2/examples/twitterdemo/main.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +#include "tweeter.h" + +const char OPT_OAUTH[] = "-o"; +const char OPT_XAUTH[] = "-x"; +const char OPT_USERNAME[] = "-u"; +const char OPT_PASSWORD[] = "-p"; +const char OPT_STATUS[] = "-m"; + +const char USAGE[] = "\n" + "Usage: tweetdemo [OPTION]...\n" + "Get OAuth access tokens from Twitter's OAuth service and " + "(optionally) post a status update on a user's timeline\n" + "\nOptions:\n" + " %1\t\tLink with Twitter OAuth service, i.e get access tokens\n" + " %2\t\tLink with Twitter XAuth service, i.e get access tokens using the XAuth protocol\n" + " %3 \tTwitter username to be used while using XAuth (-x option)\n" + " %4 \tTwitter password to be used while using XAuth (-x option)\n" + " %5\t\tStatus update message, enclosed in double quotes\n"; + + +class Helper : public QObject +{ + Q_OBJECT + +public: + Helper() : QObject(), tweeter_(this), waitForMsg_(false), msg_(QString()) {} + +public slots: + + void processArgs() { + + QStringList argList = qApp->arguments(); + + QByteArray help = QString(USAGE).arg(OPT_OAUTH, + OPT_XAUTH, + OPT_USERNAME, + OPT_PASSWORD, + OPT_STATUS).toLatin1(); + + const char* helpText = help.constData(); + + connect(&tweeter_, SIGNAL(linkingFailed()), + this, SLOT(onLinkingFailed())); + connect(&tweeter_, SIGNAL(linkingSucceeded()), + this, SLOT(onLinkingSucceeded())); + + if (argList.contains(OPT_OAUTH)) { + if (argList.contains(OPT_STATUS)) { + waitForMsg_ = true; + msg_ = argList.at(argList.indexOf(OPT_STATUS) + 1); + } + // Start OAuth + tweeter_.doOAuth(); + } else if (argList.contains(OPT_XAUTH)) { + if (!(argList.contains(OPT_USERNAME) && argList.contains(OPT_PASSWORD))) { + qDebug() << "\nError: Username or Password missing!"; + qDebug() << helpText; + qApp->exit(1); + } + + QString username = argList.at(argList.indexOf(OPT_USERNAME) + 1); + QString password = argList.at(argList.indexOf(OPT_PASSWORD) + 1); + + if (argList.contains(OPT_STATUS)) { + waitForMsg_ = true; + msg_ = argList.at(argList.indexOf(OPT_STATUS) + 1); + } + // Start XAuth + tweeter_.doXAuth(username, password); + } else if (argList.contains(OPT_STATUS)) { + QString statusMessage = argList.at(argList.indexOf(OPT_STATUS) + 1); + postStatusUpdate(statusMessage); + } else { + qDebug() << helpText; + qApp->exit(1); + } + } + + void onLinkingFailed() { + qDebug() << "Linking failed!"; + qApp->exit(1); + } + + void onLinkingSucceeded() { + qDebug() << "Linking succeeded!"; + if (waitForMsg_) { + postStatusUpdate(msg_); + } else { + qApp->quit(); + } + } + +private slots: + + void postStatusUpdate(const QString& msg) { + connect(&tweeter_, SIGNAL(statusPosted()), + qApp, SLOT(quit())); + tweeter_.postStatusUpdate(msg); + } + +private: + Tweeter tweeter_; + bool waitForMsg_; + QString msg_; +}; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + Helper helper; + QTimer::singleShot(0, &helper, SLOT(processArgs())); + + return a.exec(); +} + +#include "main.moc" diff --git a/3rdparty/o2/examples/twitterdemo/tweeter.cpp b/3rdparty/o2/examples/twitterdemo/tweeter.cpp new file mode 100644 index 00000000..bfba23b4 --- /dev/null +++ b/3rdparty/o2/examples/twitterdemo/tweeter.cpp @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +#include "tweeter.h" +#include "o2globals.h" +#include "o1requestor.h" +#include "o2settingsstore.h" + +const char O2_CONSUMER_KEY[] = "2vHeyIxjywIadjEhvbDpg"; +const char O2_CONSUMER_SECRET[] = "Xfwe195Kp3ZpcCKgkYs7RKfugTm8EfpLkQvsKfX2vvs"; + +const int localPort = 8888; + +Tweeter::Tweeter(QObject *parent) : + QObject(parent) { +} + +void Tweeter::doOAuth() { + o1Twitter_ = new O1Twitter(this); + o1Twitter_->setClientId(O2_CONSUMER_KEY); + o1Twitter_->setClientSecret(O2_CONSUMER_SECRET); + o1Twitter_->setLocalPort(localPort); + + // Create a store object for writing the received tokens + O2SettingsStore *store = new O2SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("twitter"); + o1Twitter_->setStore(store); + + // Connect signals + connect(o1Twitter_, SIGNAL(linkedChanged()), + this, SLOT(onLinkedChanged())); + connect(o1Twitter_, SIGNAL(linkingFailed()), + this, SIGNAL(linkingFailed())); + connect(o1Twitter_, SIGNAL(linkingSucceeded()), + this, SLOT(onLinkingSucceeded())); + connect(o1Twitter_, SIGNAL(openBrowser(QUrl)), + this, SLOT(onOpenBrowser(QUrl))); + connect(o1Twitter_, SIGNAL(closeBrowser()), + this, SLOT(onCloseBrowser())); + + qDebug() << "Starting OAuth..."; + o1Twitter_->link(); +} + +void Tweeter::doXAuth(const QString &username, const QString &password) { + oxTwitter_ = new OXTwitter(this); + oxTwitter_->setClientId(O2_CONSUMER_KEY); + oxTwitter_->setClientSecret(O2_CONSUMER_SECRET); + oxTwitter_->setLocalPort(localPort); + + oxTwitter_->setUsername(username); + oxTwitter_->setPassword(password); + + // Create a store object for writing the received tokens + O2SettingsStore *store = new O2SettingsStore(O2_ENCRYPTION_KEY); + store->setGroupKey("twitter"); + oxTwitter_->setStore(store); + + connect(oxTwitter_, SIGNAL(linkedChanged()), + this, SLOT(onLinkedChanged())); + connect(oxTwitter_, SIGNAL(linkingFailed()), + this, SIGNAL(linkingFailed())); + connect(oxTwitter_, SIGNAL(linkingSucceeded()), + this, SLOT(onLinkingSucceeded())); + connect(oxTwitter_, SIGNAL(openBrowser(QUrl)), + this, SLOT(onOpenBrowser(QUrl))); + connect(oxTwitter_, SIGNAL(closeBrowser()), + this, SLOT(onCloseBrowser())); + + qDebug() << "Starting XAuth..."; + qDebug() << "Username:" << username << "Password:" << password; + oxTwitter_->link(); +} + +void Tweeter::postStatusUpdate(const QString &message) { + if (!o1Twitter_->linked()) { + qWarning() << "Application is not linked to Twitter!"; + emit statusPosted(); + return; + } + + qDebug() << "Status update message:" << message.toLatin1().constData(); + + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + O1Twitter* o1 = o1Twitter_; + O1Requestor* requestor = new O1Requestor(manager, o1, this); + + QByteArray paramName("status"); + + QList reqParams = QList(); + reqParams << O1RequestParameter(paramName, message.toLatin1()); + + QByteArray postData = O1::createQueryParams(reqParams); + + QUrl url = QUrl("https://api.twitter.com/1.1/statuses/update.json"); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + + QNetworkReply *reply = requestor->post(request, reqParams, postData); + connect(reply, SIGNAL(finished()), this, SLOT(tweetReplyDone())); +} + +void Tweeter::onOpenBrowser(const QUrl &url) { + qDebug() << "Opening browser with url" << url.toString(); + QDesktopServices::openUrl(url); +} + +void Tweeter::onCloseBrowser() { +} + +void Tweeter::onLinkedChanged() { + qDebug() << "Link changed!"; +} + +void Tweeter::onLinkingSucceeded() { + O1Twitter* o1t = qobject_cast(sender()); + + QVariantMap extraTokens = o1t->extraTokens(); + + if (!extraTokens.isEmpty()) { + emit extraTokensReady(extraTokens); + + qDebug() << "Extra tokens in response:"; + foreach (QString key, extraTokens.keys()) { + qDebug() << "\t" << key << ":" << extraTokens.value(key).toString(); + } + } + emit linkingSucceeded(); +} + +void Tweeter::tweetReplyDone() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (reply->error() != QNetworkReply::NoError) { + qDebug() << "ERROR:" << reply->errorString(); + qDebug() << "content:" << reply->readAll(); + } else { + qDebug() << "Tweet posted sucessfully!"; + } + emit statusPosted(); +} diff --git a/3rdparty/o2/examples/twitterdemo/tweeter.h b/3rdparty/o2/examples/twitterdemo/tweeter.h new file mode 100644 index 00000000..6108725b --- /dev/null +++ b/3rdparty/o2/examples/twitterdemo/tweeter.h @@ -0,0 +1,41 @@ +#ifndef TWEETER_H +#define TWEETER_H + +#include +#include +#include +#include + +#include "o1twitter.h" +#include "oxtwitter.h" + +class Tweeter : public QObject +{ + Q_OBJECT +public: + explicit Tweeter(QObject *parent = 0); + +signals: + void extraTokensReady(const QVariantMap &extraTokens); + void linkingFailed(); + void linkingSucceeded(); + void statusPosted(); + +public slots: + void doOAuth(); + void doXAuth(const QString &username, const QString &password); + void postStatusUpdate(const QString &message); + +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onOpenBrowser(const QUrl &url); + void onCloseBrowser(); + void tweetReplyDone(); + +private: + O1Twitter* o1Twitter_; + OXTwitter* oxTwitter_; +}; + +#endif // TWEETER_H diff --git a/3rdparty/o2/examples/twitterdemo/twitterdemo.pro b/3rdparty/o2/examples/twitterdemo/twitterdemo.pro new file mode 100644 index 00000000..ee32bfee --- /dev/null +++ b/3rdparty/o2/examples/twitterdemo/twitterdemo.pro @@ -0,0 +1,14 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +include(../../src/src.pri) + +TARGET = twitterdemo +TEMPLATE = app + +SOURCES += main.cpp \ + tweeter.cpp + +HEADERS += \ + tweeter.h diff --git a/3rdparty/o2/src/CMakeLists.txt b/3rdparty/o2/src/CMakeLists.txt new file mode 100644 index 00000000..698ba6ce --- /dev/null +++ b/3rdparty/o2/src/CMakeLists.txt @@ -0,0 +1,98 @@ +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(WITH_QT5) + find_package(Qt5 COMPONENTS Core Network Script REQUIRED) +else(WITH_QT5) + set(QT_USE_QTNETWORK true) + set(QT_USE_QTSCRIPT true) + find_package(Qt4 REQUIRED) +endif(WITH_QT5) +#find_package(QJson REQUIRED) + +if (NOT WITH_QT5) + include( ${QT_USE_FILE} ) +endif(NOT WITH_QT5) + +set( o2_SRCS + o2.cpp + o2reply.cpp + o2replyserver.cpp + o2requestor.cpp + simplecrypt.cpp + o2settingsstore.cpp + o2abstractstore.h +) + +if(WITH_TWITTER OR WITH_DROPBOX OR WITH_FLICKR) + set( o2_SRCS + ${o2_SRCS} + o1.cpp + o1requestor.cpp + ) +endif(WITH_TWITTER OR WITH_DROPBOX OR WITH_FLICKR) + +if(WITH_TWITTER) + set( o2_SRCS + ${o2_SRCS} + o1twitter.h + oxtwitter.cpp + ) +endif(WITH_TWITTER) + +if(WITH_DROPBOX) + set( o2_SRCS + ${o2_SRCS} + o1dropbox.h + ) +endif(WITH_DROPBOX) + +if(WITH_GOOGLE) + set( o2_SRCS + ${o2_SRCS} + o2gft.cpp + ) +endif(WITH_GOOGLE) + +if(WITH_FACEBOOK) + set( o2_SRCS + ${o2_SRCS} + o2facebook.cpp + ) +endif(WITH_FACEBOOK) + +if(WITH_SKYDRIVE) + set( o2_SRCS + ${o2_SRCS} + o2skydrive.cpp + ) +endif(WITH_SKYDRIVE) + +if(WITH_FLICKR) + set( o2_SRCS + ${o2_SRCS} + o1flickr.h + ) +endif(WITH_FLICKR) + +if(WITH_HUBIC) + set( o2_SRCS + ${o2_SRCS} + o2hubic.cpp + ) +endif(WITH_HUBIC) + +if(NOT WITH_QT5) + add_definitions(${QT4_DEFINITIONS}) +endif(NOT WITH_QT5) + +add_library( o2 ${o2_SRCS} ) + +if(WITH_QT5) + target_link_libraries( o2 Qt5::Core Qt5::Network Qt5::Script) +else(WITH_QT5) + target_link_libraries( o2 ${QT_LIBRARIES} ) +endif(WITH_QT5) diff --git a/3rdparty/o2/src/o1.cpp b/3rdparty/o2/src/o1.cpp new file mode 100644 index 00000000..168e1ad2 --- /dev/null +++ b/3rdparty/o2/src/o1.cpp @@ -0,0 +1,447 @@ +#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 "o2globals.h" +#include "o2settingsstore.h" + +#define trace() if (1) qDebug() +// #define trace() if (0) qDebug() + +O1::O1(QObject *parent) : + QObject(parent) { + setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); + manager_ = new QNetworkAccessManager(this); + replyServer_ = new O2ReplyServer(this); + localPort_ = 0; + qRegisterMetaType("QNetworkReply::NetworkError"); + connect(replyServer_, SIGNAL(verificationReceived(QMap)), + this, SLOT(onVerificationReceived(QMap))); + store_ = new O2SettingsStore(O2_ENCRYPTION_KEY, this); +} + +O1::~O1() { +} + +void O1::setStore(O2AbstractStore *store) { + if (!store) { + qWarning() << "Store object is null! Using default O2SettingsStore"; + return; + } + // Delete the previously stored object + store_->deleteLater(); + store_ = store; + // re-parent it to this class as we take ownership of it now + store_->setParent(this); +} + +bool O1::linked() { + return !token().isEmpty(); +} + +QString O1::tokenSecret() { + QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); + return store_->value(key); +} + +void O1::setTokenSecret(const QString &v) { + QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); + store_->setValue(key, v); +} + +QString O1::token() { + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + return store_->value(key); +} + +void O1::setToken(const QString &v) { + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + store_->setValue(key, v); +} + +QString O1::clientId() { + return clientId_; +} + +void O1::setClientId(const QString &value) { + clientId_ = value; + emit clientIdChanged(); +} + +QString O1::clientSecret() { + return clientSecret_; +} + +void O1::setClientSecret(const QString &value) { + clientSecret_ = value; + emit clientSecretChanged(); +} + +int O1::localPort() { + return localPort_; +} + +void O1::setLocalPort(int value) { + localPort_ = value; + emit localPortChanged(); +} + +QUrl O1::requestTokenUrl() { + return requestTokenUrl_; +} + +void O1::setRequestTokenUrl(const QUrl &v) { + requestTokenUrl_ = v; + emit requestTokenUrlChanged(); +} + +QUrl O1::authorizeUrl() { + return authorizeUrl_; +} + +void O1::setAuthorizeUrl(const QUrl &value) { + authorizeUrl_ = value; + emit authorizeUrlChanged(); +} + +QUrl O1::accessTokenUrl() { + return accessTokenUrl_; +} + +void O1::setAccessTokenUrl(const QUrl &value) { + accessTokenUrl_ = value; + emit accessTokenUrlChanged(); +} + +QString O1::signatureMethod() +{ + return signatureMethod_; +} + +void O1::setSignatureMethod(const QString &value) +{ + signatureMethod_ = value; +} + +QVariantMap O1::extraTokens() const { + return extraTokens_; +} + +void O1::setExtraTokens(QVariantMap extraTokens) { + extraTokens_ = extraTokens; +} + +void O1::unlink() { + trace() << "O1::unlink"; + if (linked()) { + setToken(""); + setTokenSecret(""); + emit linkedChanged(); + } + 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(createQueryParams(headers)); +} + +/// Construct query string from list of headers +QByteArray O1::createQueryParams(const QList ¶ms) { + QByteArray ret; + bool first = true; + foreach (O1RequestParameter h, params) { + if (first) { + first = false; + } else { + ret.append("&"); + } + ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value)); + } + return ret; +} + +/// Build a base string for signing. +QByteArray O1::getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) { + QByteArray base; + + // Initialize base string with the operation name (e.g. "GET") and the base URL + base.append(getOperationName(op).toUtf8() + "&"); + base.append(QUrl::toPercentEncoding(url.toString(QUrl::RemoveQuery)) + "&"); + + // Append a sorted+encoded list of all request parameters to the base string + QList headers(oauthParams); + headers.append(otherParams); + qSort(headers); + base.append(encodeHeaders(headers)); + + return base; +} + +QByteArray O1::sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) { + QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op); + QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret); +#if QT_VERSION >= 0x050100 + return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha1).toBase64(); +#else + return hmacSha1(secret, baseString); +#endif +} + +QByteArray O1::buildAuthorizationHeader(const QList &oauthParams) { + bool first = true; + QByteArray ret("OAuth "); + QList headers(oauthParams); + qSort(headers); + foreach (O1RequestParameter h, headers) { + if (first) { + first = false; + } else { + ret.append(","); + } + ret.append(h.name); + ret.append("=\""); + ret.append(QUrl::toPercentEncoding(h.value)); + ret.append("\""); + } + return ret; +} + +QByteArray O1::generateSignature(const QList headers, + const QNetworkRequest &req, + const QList &signingParameters, + QNetworkAccessManager::Operation operation) +{ + QByteArray signature; + + if(signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA1) + { + signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret()); + } + else if(signatureMethod() == O2_SIGNATURE_TYPE_PLAINTEXT) + { + signature = clientSecret().toLatin1() + "&" + tokenSecret().toLatin1(); + } + + return signature; +} + +void O1::link() { + trace() << "O1::link"; + if (linked()) { + trace() << "O1::link: Linked already"; + emit linkingSucceeded(); + return; + } + + // Start reply server + replyServer_->listen(QHostAddress::Any, localPort()); + + // Create request + QNetworkRequest request(requestTokenUrl()); + + // Create initial token request + QList headers; + headers.append(O1RequestParameter(O2_OAUTH_CALLBACK, QString(O2_CALLBACK_URL).arg(replyServer_->serverPort()).toLatin1())); + headers.append(O1RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); + headers.append(O1RequestParameter(O2_OAUTH_NONCE, nonce())); + headers.append(O1RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + headers.append(O1RequestParameter(O2_OAUTH_VERSION, "1.0")); + headers.append(O1RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); + headers.append(O1RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, QList(), QNetworkAccessManager::PostOperation))); + + // Clear request token + requestToken_.clear(); + requestTokenSecret_.clear(); + + // Post request + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(headers)); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QNetworkReply *reply = manager_->post(request, QByteArray()); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished())); +} + +void O1::onTokenRequestError(QNetworkReply::NetworkError error) { + QNetworkReply *reply = qobject_cast(sender()); + qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll(); + emit linkingFailed(); +} + +void O1::onTokenRequestFinished() { + trace() << "O1::onTokenRequestFinished"; + QNetworkReply *reply = qobject_cast(sender()); + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + 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; + 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, QString(O2_CALLBACK_URL).arg(replyServer_->serverPort()).toLatin1()); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH_TOKEN, requestToken_); + query.addQueryItem(O2_OAUTH_CALLBACK, QString(O2_CALLBACK_URL).arg(replyServer_->serverPort()).toLatin1()); + url.setQuery(query); +#endif + emit openBrowser(url); +} + +void O1::onVerificationReceived(QMap params) { + trace() << "O1::onVerificationReceived"; + 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"; + emit linkingFailed(); + } +} + +void O1::exchangeToken() { + // Create token exchange request + + QNetworkRequest request(accessTokenUrl()); + + QList oauthParams; + oauthParams.append(O1RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_VERSION, "1.0")); + oauthParams.append(O1RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_NONCE, nonce())); + oauthParams.append(O1RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList(), QNetworkAccessManager::PostOperation))); + + // Post request + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QNetworkReply *reply = manager_->post(request, QByteArray()); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); +} + +void O1::onTokenExchangeError(QNetworkReply::NetworkError error) { + QNetworkReply *reply = qobject_cast(sender()); + qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll(); + emit linkingFailed(); +} + +void O1::onTokenExchangeFinished() { + QNetworkReply *reply = qobject_cast(sender()); + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + return; + } + + // Get access token and secret + QByteArray data = reply->readAll(); + QMap response = parseResponse(data); + if (response.contains(O2_OAUTH_TOKEN) && response.contains(O2_OAUTH_TOKEN_SECRET)) { + setToken(response.take(O2_OAUTH_TOKEN)); + setTokenSecret(response.take(O2_OAUTH_TOKEN_SECRET)); + // Set extra tokens if any + if (!response.isEmpty()) { + QVariantMap extraTokens; + foreach (QString key, response.keys()) { + extraTokens.insert(key, response.value(key)); + } + setExtraTokens(extraTokens); + } + emit linkedChanged(); + emit linkingSucceeded(); + } else { + qWarning() << "O1::onTokenExchangeFinished: oauth_token or oauth_token_secret missing from response" << data; + 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/3rdparty/o2/src/o1.h b/3rdparty/o2/src/o1.h new file mode 100644 index 00000000..99d2fd7c --- /dev/null +++ b/3rdparty/o2/src/o1.h @@ -0,0 +1,208 @@ +#ifndef O1_H +#define O1_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "o2abstractstore.h" + +class O2ReplyServer; + +/// Request parameter (name-value pair) participating in authentication. +struct O1RequestParameter { + O1RequestParameter(const QByteArray &n, const QByteArray &v): name(n), value(v) { + } + bool operator <(const O1RequestParameter &other) const { + return (name == other.name)? (value < other.value): (name < other.name); + } + QByteArray name; + QByteArray value; +}; + +/// Simple OAuth 1.0 authenticator. +class O1: public QObject { + Q_OBJECT + +public: + /// Are we authenticated? + Q_PROPERTY(bool linked READ linked NOTIFY linkedChanged) + bool linked(); + + /// Authentication token. + QString token(); + + /// Authentication token secret. + QString tokenSecret(); + + /// Extra tokens available after a successful OAuth exchange + Q_PROPERTY(QMap extraTokens READ extraTokens) + QVariantMap extraTokens() const; + + /// 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); + + /// Token request URL. + Q_PROPERTY(QUrl requestTokenUrl READ requestTokenUrl WRITE setRequestTokenUrl NOTIFY requestTokenUrlChanged) + QUrl requestTokenUrl(); + void setRequestTokenUrl(const QUrl &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); + + /// Signature method + Q_PROPERTY(QString signatureMethod READ signatureMethod WRITE setSignatureMethod NOTIFY signatureMethodChanged) + QString signatureMethod(); + void setSignatureMethod(const QString &value); + + /// TCP port number to use in local redirections. + /// The OAuth "redirect_uri" will be set to "http://localhost:/". + /// If localPort is set to 0 (default), O1 will replace it with a free one. + Q_PROPERTY(int localPort READ localPort WRITE setLocalPort NOTIFY localPortChanged) + int localPort(); + void setLocalPort(int value); + + /// Constructor. + /// @param parent Parent object. + explicit O1(QObject *parent = 0); + + /// Destructor. + virtual ~O1(); + + /// Sets the storage object to use for storing the OAuth tokens on a peristent medium + void setStore(O2AbstractStore *store); + + /// Parse a URL-encoded response string. + static QMap parseResponse(const QByteArray &response); + + /// Build the value of the "Authorization:" header. + static QByteArray buildAuthorizationHeader(const QList &oauthParams); + + /// Create unique bytes to prevent replay attacks. + static QByteArray nonce(); + + + /// Generate signature string depending on signature method type + QByteArray generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation); + + /// Calculate the HMAC-SHA1 signature of a request. + /// @param oauthParams OAuth parameters. + /// @param otherParams Other parameters participating in signing. + /// @param URL Request URL. May contain query parameters, but they will not be used for signing. + /// @param op HTTP operation. + /// @param consumerSecret Consumer (application) secret. + /// @param tokenSecret Authorization token secret (empty if not yet available). + /// @return Signature that can be used as the value of the "oauth_signature" parameter. + static QByteArray sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret); + + /// Build a base string for signing. + static QByteArray getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op); + + /// Build a concatenated/percent-encoded string from a list of headers. + static QByteArray encodeHeaders(const QList &headers); + + /// Construct query string from list of headers + static QByteArray createQueryParams(const QList ¶ms); + +public slots: + /// Authenticate. + Q_INVOKABLE virtual void link(); + + /// De-authenticate. + Q_INVOKABLE virtual void unlink(); + +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 requestTokenUrlChanged(); + void authorizeUrlChanged(); + void accessTokenUrlChanged(); + void localPortChanged(); + void signatureMethodChanged(); + +protected slots: + /// Handle verification received from the reply server. + virtual void onVerificationReceived(QMap params); + + /// Handle token request error. + virtual void onTokenRequestError(QNetworkReply::NetworkError error); + + /// Handle token request finished. + virtual void onTokenRequestFinished(); + + /// Handle token exchange error. + void onTokenExchangeError(QNetworkReply::NetworkError error); + + /// Handle token exchange finished. + void onTokenExchangeFinished(); + +protected: + /// Set authentication token. + void setToken(const QString &v); + + /// Set authentication token secret. + void setTokenSecret(const QString &v); + + /// Exchange token for authorizaton token. + virtual void exchangeToken(); + + /// Set extra tokens found in OAuth response + void setExtraTokens(QVariantMap extraTokens); + +protected: + QString clientId_; + QString clientSecret_; + QString scope_; + QString code_; + QString redirectUri_; + QString requestToken_; + QString requestTokenSecret_; + QString verifier_; + QString signatureMethod_; + QUrl requestTokenUrl_; + QUrl authorizeUrl_; + QUrl accessTokenUrl_; + QNetworkAccessManager *manager_; + O2ReplyServer *replyServer_; + quint16 localPort_; + O2AbstractStore *store_; + QVariantMap extraTokens_; +}; + +#endif // O1_H diff --git a/3rdparty/o2/src/o1dropbox.h b/3rdparty/o2/src/o1dropbox.h new file mode 100644 index 00000000..1f3a324d --- /dev/null +++ b/3rdparty/o2/src/o1dropbox.h @@ -0,0 +1,18 @@ +#ifndef O1DROPBOX_H +#define O1DROPBOX_H + +#include "o1.h" + +class O1Dropbox: public O1 { + Q_OBJECT + +public: + explicit O1Dropbox(QObject *parent = 0): O1(parent) { + setRequestTokenUrl(QUrl("https://api.dropbox.com/1/oauth/request_token")); + setAuthorizeUrl(QUrl("https://www.dropbox.com/1/oauth/authorize?display=mobile")); + setAccessTokenUrl(QUrl("https://api.dropbox.com/1/oauth/access_token")); + setLocalPort(1965); + } +}; + +#endif // O1DROPBOX_H diff --git a/3rdparty/o2/src/o1flickr.h b/3rdparty/o2/src/o1flickr.h new file mode 100644 index 00000000..95490aa4 --- /dev/null +++ b/3rdparty/o2/src/o1flickr.h @@ -0,0 +1,18 @@ +#ifndef O1FLICKR_H +#define O1FLICKR_H + +#include "o1.h" + +class O1Flickr: public O1 { + Q_OBJECT + +public: + explicit O1Flickr(QObject *parent = 0): O1(parent) { + setRequestTokenUrl(QUrl("http://www.flickr.com/services/oauth/request_token")); + setAuthorizeUrl(QUrl("http://www.flickr.com/services/oauth/authorize?perms=write")); + setAccessTokenUrl(QUrl("http://www.flickr.com/services/oauth/access_token")); + setLocalPort(1965); // FIXME: Really needed? + } +}; + +#endif // O1FLICKR_H diff --git a/3rdparty/o2/src/o1freshbooks.h b/3rdparty/o2/src/o1freshbooks.h new file mode 100755 index 00000000..e93fc8df --- /dev/null +++ b/3rdparty/o2/src/o1freshbooks.h @@ -0,0 +1,26 @@ +#ifndef O1FRESHBOOKS_H +#define O1FRESHBOOKS_H + +#include "o1.h" + +class O1Freshbooks: public O1 { + Q_OBJECT + +public: + explicit O1Freshbooks(QObject *parent = 0): O1(parent) + { + } + + void setClientId(const QString &value) + { + O1::setClientId(value); + + setRequestTokenUrl(QUrl("https://" + clientId() + ".freshbooks.com/oauth/oauth_request.php")); + setAuthorizeUrl(QUrl("https://" + clientId() + ".freshbooks.com/oauth/oauth_authorize.php")); + setAccessTokenUrl(QUrl("https://" + clientId() + ".freshbooks.com/oauth/oauth_access.php")); + } + + +}; + +#endif // O1FRESHBOOKS_H diff --git a/3rdparty/o2/src/o1requestor.cpp b/3rdparty/o2/src/o1requestor.cpp new file mode 100644 index 00000000..196eb1a9 --- /dev/null +++ b/3rdparty/o2/src/o1requestor.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include + +#include "o1requestor.h" +#include "o2globals.h" + +/// A timer connected to a network reply. +class TimedReply: public QTimer { + Q_OBJECT + +public: + explicit TimedReply(QNetworkReply *parent): QTimer(parent) { + setSingleShot(true); + setInterval(60 * 1000); // FIXME: Expose me + connect(this, SIGNAL(error(QNetworkReply::NetworkError)), parent, SIGNAL(error(QNetworkReply::NetworkError))); + connect(this, SIGNAL(timeout()), this, SLOT(onTimeout())); + } + +signals: + void error(QNetworkReply::NetworkError); + +public slots: + void onTimeout() {emit error(QNetworkReply::TimeoutError);} +}; + +O1Requestor::O1Requestor(QNetworkAccessManager *manager, O1 *authenticator, QObject *parent): QObject(parent) { + manager_ = manager; + authenticator_ = authenticator; +} + +QNetworkReply *O1Requestor::get(const QNetworkRequest &req, const QList &signingParameters) { + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::GetOperation); + return addTimer(manager_->get(request)); +} + +QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) { + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PostOperation); + return addTimer(manager_->post(request, data)); +} + +QNetworkReply *O1Requestor::post(const QNetworkRequest &req, const QList &signingParameters, QHttpMultiPart * multiPart) { + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PostOperation); + return addTimer(manager_->post(request, multiPart)); +} + +QNetworkReply *O1Requestor::put(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data) { + QNetworkRequest request = setup(req, signingParameters, QNetworkAccessManager::PutOperation); + return addTimer(manager_->put(request, data)); +} + +QNetworkReply *O1Requestor::addTimer(QNetworkReply *reply) { + (void)new TimedReply(reply); + return reply; +} + +QNetworkRequest O1Requestor::setup(const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) { + // Collect OAuth parameters + QList oauthParams; + oauthParams.append(O1RequestParameter(O2_OAUTH_CONSUMER_KEY, authenticator_->clientId().toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_VERSION, "1.0")); + oauthParams.append(O1RequestParameter(O2_OAUTH_TOKEN, authenticator_->token().toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE_METHOD, authenticator_->signatureMethod().toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_NONCE, O1::nonce())); + oauthParams.append(O1RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + + // Add signature parameter + oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE, authenticator_->generateSignature(oauthParams, req, signingParameters, operation))); + + // Return a copy of the original request with authorization header set + QNetworkRequest request(req); + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, O1::buildAuthorizationHeader(oauthParams)); + return request; +} + +#include "o1requestor.moc" diff --git a/3rdparty/o2/src/o1requestor.h b/3rdparty/o2/src/o1requestor.h new file mode 100644 index 00000000..7a4c283d --- /dev/null +++ b/3rdparty/o2/src/o1requestor.h @@ -0,0 +1,61 @@ +#ifndef O1REQUESTOR_H +#define O1REQUESTOR_H + +#include +#include +#include + +#include "o1.h" + +class QNetworkAccessManager; +class QNetworkReply; +class O1; + +/// Makes authenticated requests using OAuth 1.0. +class O1Requestor: public QObject { + Q_OBJECT + +public: + explicit O1Requestor(QNetworkAccessManager *manager, O1 *authenticator, QObject *parent = 0); + +public slots: + /// Make a GET request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @return Reply. + QNetworkReply *get(const QNetworkRequest &req, const QList &signingParameters); + + /// Make a POST request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @param data Request payload. + /// @return Reply. + QNetworkReply *post(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data); + + /// Make a POST request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @param multiPart HTTPMultiPart. + /// @return Reply. + QNetworkReply *post(const QNetworkRequest &req, const QList &signingParameters, QHttpMultiPart *multiPart); + + /// Make a PUT request. + /// @param req Network request. + /// @param signingParameters Extra (non-OAuth) parameters participating in signing. + /// @param data Request payload. + /// @return Reply. + QNetworkReply *put(const QNetworkRequest &req, const QList &signingParameters, const QByteArray &data); + +protected: + /// Return new request based on the original, with the "Authentication:" header added. + QNetworkRequest setup(const QNetworkRequest &request, const QList &signingParameters, QNetworkAccessManager::Operation operation); + + /// Augment reply with a timer. + QNetworkReply *addTimer(QNetworkReply *reply); + + QNetworkAccessManager *manager_; + O1 *authenticator_; +}; + + +#endif // O1REQUESTOR_H diff --git a/3rdparty/o2/src/o1twitter.h b/3rdparty/o2/src/o1twitter.h new file mode 100644 index 00000000..0fd3e9f9 --- /dev/null +++ b/3rdparty/o2/src/o1twitter.h @@ -0,0 +1,17 @@ +#ifndef O1TWITTER_H +#define O1TWITTER_H + +#include "o1.h" + +class O1Twitter: public O1 { + Q_OBJECT + +public: + explicit O1Twitter(QObject *parent = 0): O1(parent) { + 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/3rdparty/o2/src/o2.cpp b/3rdparty/o2/src/o2.cpp new file mode 100644 index 00000000..608bfb15 --- /dev/null +++ b/3rdparty/o2/src/o2.cpp @@ -0,0 +1,397 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2.h" +#include "o2replyserver.h" +#include "o2globals.h" +#include "o2settingsstore.h" + +#define trace() if (1) qDebug() +// define trace() if (0) qDebug() + +O2::O2(QObject *parent): QObject(parent) { + manager_ = new QNetworkAccessManager(this); + replyServer_ = new O2ReplyServer(this); + grantFlow_ = GrantFlowAuthorizationCode; + localPort_ = 0; + localhostPolicy_ = QString(O2_CALLBACK_URL); + qRegisterMetaType("QNetworkReply::NetworkError"); + connect(replyServer_, SIGNAL(verificationReceived(QMap)), + this, SLOT(onVerificationReceived(QMap))); + store_ = new O2SettingsStore(O2_ENCRYPTION_KEY, this); +} + +O2::~O2() { +} + +void O2::setStore(O2AbstractStore *store) { + if (!store) { + qWarning() << "Store object is null! Using default O2SettingsStore"; + return; + } + // Delete the previously stored object + store_->deleteLater(); + store_ = store; + // re-parent it to this class as we take ownership of it now + store_->setParent(this); +} + +O2::GrantFlow O2::grantFlow() { + return grantFlow_; +} + +void O2::setGrantFlow(O2::GrantFlow value) { + grantFlow_ = value; + emit grantFlowChanged(); +} + +QString O2::clientId() { + return clientId_; +} + +void O2::setClientId(const QString &value) { + clientId_ = value; + emit clientIdChanged(); +} + +QString O2::clientSecret() { + return clientSecret_; +} + +void O2::setClientSecret(const QString &value) { + clientSecret_ = value; + emit clientSecretChanged(); +} + +QString O2::scope() { + return scope_; +} + +void O2::setScope(const QString &value) { + scope_ = value; + emit scopeChanged(); +} + +QString O2::requestUrl() { + return requestUrl_.toString(); +} + +void O2::setRequestUrl(const QString &value) { + requestUrl_ = value; + emit requestUrlChanged(); +} + +QString O2::tokenUrl() { + return tokenUrl_.toString(); +} + +void O2::setTokenUrl(const QString &value) { + tokenUrl_= value; + emit tokenUrlChanged(); +} + +QString O2::refreshTokenUrl() { + return refreshTokenUrl_.toString(); +} + +void O2::setRefreshTokenUrl(const QString &value) { + refreshTokenUrl_ = value; + emit refreshTokenUrlChanged(); +} + +int O2::localPort() { + return localPort_; +} + +void O2::setLocalPort(int value) { + localPort_ = value; + emit localPortChanged(); +} + +QVariantMap O2::extraTokens() const { + return extraTokens_; +} + +void O2::setExtraTokens(QVariantMap extraTokens) { + extraTokens_ = extraTokens; +} + +void O2::link() { + trace() << "O2::link"; + if (linked()) { + trace() << " Linked already"; + emit linkingSucceeded(); + return; + } + + // Start listening to authentication replies + replyServer_->listen(QHostAddress::Any, localPort_); + + // Save redirect URI, as we have to reuse it when requesting the access token + redirectUri_ = localhostPolicy_.arg(replyServer_->serverPort()); + + // Assemble intial authentication URL + QList > parameters; + parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE), (grantFlow_ == GrantFlowAuthorizationCode) ? QString(O2_OAUTH2_CODE) : QString(O2_OAUTH2_TOKEN))); + parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_)); + parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_)); + // parameters.append(qMakePair(QString(OAUTH2_REDIRECT_URI), QString(QUrl::toPercentEncoding(redirectUri_)))); + parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_)); + + // Show authentication URL with a web browser + QUrl url(requestUrl_); +#if QT_VERSION < 0x050000 + url.setQueryItems(parameters); +#else + QUrlQuery query(url); + query.setQueryItems(parameters); + url.setQuery(query); +#endif + + trace() << "Emit openBrowser" << url.toString(); + emit openBrowser(url); +} + +void O2::unlink() { + if (!linked()) { + return; + } + setToken(QString()); + setRefreshToken(QString()); + setExpires(0); + emit linkedChanged(); + emit linkingSucceeded(); +} + +bool O2::linked() { + return token().length(); +} + +void O2::onVerificationReceived(const QMap response) { + trace() << "O2::onVerificationReceived"; + trace() << "" << response; + + emit closeBrowser(); + if (response.contains("error")) { + trace() << " Verification failed"; + emit linkingFailed(); + return; + } + + if (grantFlow_ == GrantFlowAuthorizationCode) { + // Save access code + setCode(response.value(QString(O2_OAUTH2_CODE))); + + // Exchange access code for access/refresh tokens + QNetworkRequest tokenRequest(tokenUrl_); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QMap parameters; + parameters.insert(O2_OAUTH2_CODE, code()); + parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_); + parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_); + parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE); + QByteArray data = buildRequestBody(parameters); + QNetworkReply *tokenReply = manager_->post(tokenRequest, data); + timedReplies_.add(tokenReply); + connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); + connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + } else { + setToken(response.value(O2_OAUTH2_ACCESS_TOKEN)); + setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN)); + } +} + +QString O2::code() { + QString key = QString(O2_KEY_CODE).arg(clientId_); + return store_->value(key); +} + +void O2::setCode(const QString &c) { + QString key = QString(O2_KEY_CODE).arg(clientId_); + store_->setValue(key, c); +} + +void O2::onTokenReplyFinished() { + trace() << "O2::onTokenReplyFinished"; + QNetworkReply *tokenReply = qobject_cast(sender()); + if (tokenReply->error() == QNetworkReply::NoError) { + QByteArray replyData = tokenReply->readAll(); + QScriptEngine engine; + QScriptValueIterator it(engine.evaluate("(" + QString(replyData) + ")")); + QVariantMap tokens; + + while (it.hasNext()) { + it.next(); + tokens.insert(it.name(), it.value().toVariant()); + } + // Check for mandatory tokens + if (tokens.contains(O2_OAUTH2_ACCESS_TOKEN)) { + setToken(tokens.take(O2_OAUTH2_ACCESS_TOKEN).toString()); + bool ok = false; + int expiresIn = tokens.take(O2_OAUTH2_EXPIRES_IN).toInt(&ok); + if (ok) { + trace() << "Token expires in" << expiresIn << "seconds"; + setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn); + } + setRefreshToken(tokens.take(O2_OAUTH2_REFRESH_TOKEN).toString()); + // Set extra tokens if any + if (!tokens.isEmpty()) { + setExtraTokens(tokens); + } + timedReplies_.remove(tokenReply); + emit linkedChanged(); + emit tokenChanged(); + emit linkingSucceeded(); + } else { + qWarning() << "O2::onTokenReplyFinished: oauth_token missing from response" << replyData; + emit linkedChanged(); + emit tokenChanged(); + emit linkingFailed(); + } + } + tokenReply->deleteLater(); +} + +void O2::onTokenReplyError(QNetworkReply::NetworkError error) { + QNetworkReply *tokenReply = qobject_cast(sender()); + trace() << "O2::onTokenReplyError" << error << tokenReply->errorString(); + trace() << "" << tokenReply->readAll(); + setToken(QString()); + setRefreshToken(QString()); + timedReplies_.remove(tokenReply); + emit linkedChanged(); + emit tokenChanged(); + 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; +} + +QString O2::token() { + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + return store_->value(key); +} + +void O2::setToken(const QString &v) { + QString key = QString(O2_KEY_TOKEN).arg(clientId_); + store_->setValue(key, v); +} + +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) { + trace() << "O2::setRefreshToken" << v.left(4) << "..."; + QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_); + store_->setValue(key, v); +} + +void O2::refresh() { + trace() << "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()); + trace() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); + if (refreshReply->error() == QNetworkReply::NoError) { + QByteArray reply = refreshReply->readAll(); + QScriptValue value; + QScriptEngine engine; + value = engine.evaluate("(" + QString(reply) + ")"); + setToken(value.property(O2_OAUTH2_ACCESS_TOKEN).toString()); + setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + value.property(O2_OAUTH2_EXPIRES_IN).toInteger()); + setRefreshToken(value.property(O2_OAUTH2_REFRESH_TOKEN).toString()); + timedReplies_.remove(refreshReply); + emit linkingSucceeded(); + emit tokenChanged(); + emit linkedChanged(); + emit refreshFinished(QNetworkReply::NoError); + trace() << " New token expires in" << expires() << "seconds"; + } + refreshReply->deleteLater(); +} + +void O2::onRefreshError(QNetworkReply::NetworkError error) { + QNetworkReply *refreshReply = qobject_cast(sender()); + qWarning() << "O2::onRefreshFailed: Error" << error << ", resetting tokens"; + setToken(QString()); + setRefreshToken(QString()); + timedReplies_.remove(refreshReply); + emit tokenChanged(); + emit linkingFailed(); + emit linkedChanged(); + emit refreshFinished(error); +} + +QString O2::localhostPolicy() const +{ + return localhostPolicy_; +} + +void O2::setLocalhostPolicy(const QString& value) +{ + localhostPolicy_ = value; +} diff --git a/3rdparty/o2/src/o2.h b/3rdparty/o2/src/o2.h new file mode 100644 index 00000000..7962aa64 --- /dev/null +++ b/3rdparty/o2/src/o2.h @@ -0,0 +1,199 @@ +#ifndef O2_H +#define O2_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "o2reply.h" +#include "o2abstractstore.h" + +class O2ReplyServer; + +/// Simple OAuth2 authenticator. +class O2: public QObject { + Q_OBJECT + Q_ENUMS(GrantFlow) + +public: + enum GrantFlow {GrantFlowAuthorizationCode, GrantFlowImplicit}; + + /// Authorization flow: Authorization Code (default, see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1) or Implicit (see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.2) + Q_PROPERTY(GrantFlow grantFlow READ grantFlow WRITE setGrantFlow NOTIFY grantFlowChanged) + GrantFlow grantFlow(); + void setGrantFlow(GrantFlow value); + + /// Are we authenticated? + Q_PROPERTY(bool linked READ linked NOTIFY linkedChanged) + bool linked(); + + /// Extra tokens available after a successful OAuth exchange + Q_PROPERTY(QMap extraTokens READ extraTokens) + QVariantMap extraTokens() const; + + /// Authentication token. + Q_PROPERTY(QString token READ token WRITE setToken NOTIFY tokenChanged) + QString token(); + void setToken(const QString &v); + + /// Client ID. + /// O2 instances with the same (client ID, client secret) share the same "linked" and "token" properties. + Q_PROPERTY(QString clientId READ clientId WRITE setClientId NOTIFY clientIdChanged) + QString clientId(); + void setClientId(const QString &value); + + /// Client secret. + /// O2 instances with the same (client ID, client secret) share the same "linked" and "token" properties. + Q_PROPERTY(QString clientSecret READ clientSecret WRITE setClientSecret NOTIFY clientSecretChanged) + QString clientSecret(); + void setClientSecret(const QString &value); + + /// Scope of authentication. + Q_PROPERTY(QString scope READ scope WRITE setScope NOTIFY scopeChanged) + QString scope(); + void setScope(const QString &value); + + /// Request URL. + Q_PROPERTY(QString requestUrl READ requestUrl WRITE setRequestUrl NOTIFY requestUrlChanged) + QString requestUrl(); + void setRequestUrl(const QString &value); + + /// Token request URL. + Q_PROPERTY(QString tokenUrl READ tokenUrl WRITE setTokenUrl NOTIFY tokenUrlChanged) + QString tokenUrl(); + void setTokenUrl(const QString &value); + + /// Token refresh URL. + Q_PROPERTY(QString refreshTokenUrl READ refreshTokenUrl WRITE setRefreshTokenUrl NOTIFY refreshTokenUrlChanged) + QString refreshTokenUrl(); + void setRefreshTokenUrl(const QString &value); + + /// TCP port number to use in local redirections. + /// The OAuth 2.0 "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); + + /// Localhost policy. By default it's value is http://127.0.0.1:%1/, however some services may + /// require the use of http://localhost:%1/ or any other value. + Q_PROPERTY(QString localhostPolicy READ localhostPolicy WRITE setLocalhostPolicy) + QString localhostPolicy() const; + void setLocalhostPolicy(const QString &value); + +public: + /// Constructor. + /// @param parent Parent object. + explicit O2(QObject *parent = 0); + + /// Destructor. + virtual ~O2(); + + /// Get authentication code. + QString code(); + + /// Get refresh token. + QString refreshToken(); + + /// Get token expiration time (seconds from Epoch). + int expires(); + + /// Sets the storage object to use for storing the OAuth tokens on a peristent medium + void setStore(O2AbstractStore *store); + +public slots: + /// Authenticate. + Q_INVOKABLE virtual void link(); + + /// De-authenticate. + Q_INVOKABLE virtual void unlink(); + + /// Refresh token. + void refresh(); + +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(); + + /// Emitted when a token refresh has been completed or failed. + void refreshFinished(QNetworkReply::NetworkError error); + + // Property change signals + void grantFlowChanged(); + void linkedChanged(); + void tokenChanged(); + void clientIdChanged(); + void clientSecretChanged(); + void scopeChanged(); + void requestUrlChanged(); + void tokenUrlChanged(); + void refreshTokenUrlChanged(); + void localPortChanged(); + +protected slots: + /// Handle verification response. + virtual void onVerificationReceived(QMap); + + /// Handle completion of a token request. + virtual void onTokenReplyFinished(); + + /// Handle failure of a token request. + virtual void onTokenReplyError(QNetworkReply::NetworkError error); + + /// Handle completion of a refresh request. + virtual void onRefreshFinished(); + + /// Handle failure of a refresh request. + virtual void onRefreshError(QNetworkReply::NetworkError error); + +protected: + /// Build HTTP request body. + QByteArray buildRequestBody(const QMap ¶meters); + + /// Set authentication code. + void setCode(const QString &v); + + /// Set refresh token. + void setRefreshToken(const QString &v); + + /// Set token expiration time. + void setExpires(int v); + + /// Set extra tokens found in OAuth response + void setExtraTokens(QVariantMap extraTokens); + +protected: + QString clientId_; + QString clientSecret_; + QString scope_; + QString code_; + QString redirectUri_; + QString localhostPolicy_; + QUrl requestUrl_; + QUrl tokenUrl_; + QUrl refreshTokenUrl_; + QNetworkAccessManager *manager_; + O2ReplyServer *replyServer_; + O2ReplyList timedReplies_; + quint16 localPort_; + GrantFlow grantFlow_; + O2AbstractStore *store_; + QVariantMap extraTokens_; +}; + +#endif // O2_H diff --git a/3rdparty/o2/src/o2abstractstore.h b/3rdparty/o2/src/o2abstractstore.h new file mode 100644 index 00000000..2e52fb88 --- /dev/null +++ b/3rdparty/o2/src/o2abstractstore.h @@ -0,0 +1,24 @@ +#ifndef O2ABSTRACTSTORE_H +#define O2ABSTRACTSTORE_H + +#include +#include + +class O2AbstractStore: public QObject +{ + Q_OBJECT + +public: + + explicit O2AbstractStore(QObject *parent = 0): QObject(parent) { + } + + virtual ~O2AbstractStore() { + } + + virtual QString value(const QString &key, const QString &defaultValue = QString()) = 0; + + virtual void setValue(const QString &key, const QString &value) = 0; +}; + +#endif // O2ABSTRACTSTORE_H diff --git a/3rdparty/o2/src/o2facebook.cpp b/3rdparty/o2/src/o2facebook.cpp new file mode 100644 index 00000000..43ca0754 --- /dev/null +++ b/3rdparty/o2/src/o2facebook.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2facebook.h" +#include "o2globals.h" + +static const char *FbEndpoint = "https://graph.facebook.com/oauth/authorize?display=touch"; +static const char *FbTokenUrl = "https://graph.facebook.com/oauth/access_token"; +static const quint16 FbLocalPort = 1965; + +const char FB_EXPIRES_KEY[] = "expires"; + +O2Facebook::O2Facebook(QObject *parent): O2(parent) { + setRequestUrl(FbEndpoint); + setTokenUrl(FbTokenUrl); + setLocalPort(FbLocalPort); +} + +void O2Facebook::onVerificationReceived(const QMap response) { + emit closeBrowser(); + if (response.contains("error")) { + qWarning() << "O2Facebook::onVerificationReceived: Verification failed"; + foreach (QString key, response.keys()) { + qWarning() << "O2Facebook::onVerificationReceived:" << key << response.value(key); + } + emit linkingFailed(); + return; + } + + // Save access code + setCode(response.value(O2_OAUTH2_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_SCOPE, scope_); + url.addQueryItem(O2_OAUTH2_CODE, code()); + url.addQueryItem(O2_OAUTH2_REDIRECT_URI, redirectUri_); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_CLIENT_ID, clientId_); + query.addQueryItem(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + query.addQueryItem(O2_OAUTH2_SCOPE, scope_); + query.addQueryItem(O2_OAUTH2_CODE, code()); + query.addQueryItem(O2_OAUTH2_REDIRECT_URI, redirectUri_); + url.setQuery(query); +#endif + + QNetworkRequest tokenRequest(url); + QNetworkReply *tokenReply = manager_->get(tokenRequest); + 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 O2Facebook::onTokenReplyFinished() { + QNetworkReply *tokenReply = qobject_cast(sender()); + if (tokenReply->error() == QNetworkReply::NoError) { + + // Process reply + QByteArray replyData = tokenReply->readAll(); + QMap reply; + foreach (QString pair, QString(replyData).split("&")) { + QStringList kv = pair.split("="); + if (kv.length() == 2) { + reply.insert(kv[0], kv[1]); + } + } + + // Interpret reply + setToken(reply.value(O2_OAUTH2_ACCESS_TOKEN, "")); + setExpires(reply.value(FB_EXPIRES_KEY).toInt()); + setRefreshToken(reply.value(O2_OAUTH2_REFRESH_TOKEN, "")); + + timedReplies_.remove(tokenReply); + emit linkedChanged(); + emit tokenChanged(); + emit linkingSucceeded(); + } +} + +void O2Facebook::unlink() { + O2::unlink(); + // FIXME: Delete relevant cookies, too +} diff --git a/3rdparty/o2/src/o2facebook.h b/3rdparty/o2/src/o2facebook.h new file mode 100644 index 00000000..289942c8 --- /dev/null +++ b/3rdparty/o2/src/o2facebook.h @@ -0,0 +1,21 @@ +#ifndef O2FACEBOOK_H +#define O2FACEBOOK_H + +#include "o2.h" + +/// Facebook's dialect of OAuth 2.0 +class O2Facebook: public O2 { + Q_OBJECT + +public: + explicit O2Facebook(QObject *parent = 0); + +public slots: + Q_INVOKABLE void unlink(); + +protected slots: + void onVerificationReceived(QMap); + virtual void onTokenReplyFinished(); +}; + +#endif // O2FACEBOOK_H diff --git a/3rdparty/o2/src/o2gft.cpp b/3rdparty/o2/src/o2gft.cpp new file mode 100644 index 00000000..ce1b7dbf --- /dev/null +++ b/3rdparty/o2/src/o2gft.cpp @@ -0,0 +1,13 @@ +#include "o2gft.h" + +static const char *GftScope = "https://www.googleapis.com/auth/fusiontables"; +static const char *GftEndpoint = "https://accounts.google.com/o/oauth2/auth"; +static const char *GftTokenUrl = "https://accounts.google.com/o/oauth2/token"; +static const char *GftRefreshUrl = "https://accounts.google.com/o/oauth2/token"; + +O2Gft::O2Gft(QObject *parent): O2(parent) { + setRequestUrl(GftEndpoint); + setTokenUrl(GftTokenUrl); + setRefreshTokenUrl(GftRefreshUrl); + setScope(GftScope); +} diff --git a/3rdparty/o2/src/o2gft.h b/3rdparty/o2/src/o2gft.h new file mode 100644 index 00000000..fa6dc41b --- /dev/null +++ b/3rdparty/o2/src/o2gft.h @@ -0,0 +1,14 @@ +#ifndef O2GFT_H +#define O2GFT_H + +#include "o2.h" + +/// Google Fusion Tables' dialect of OAuth 2.0 +class O2Gft: public O2 { + Q_OBJECT + +public: + explicit O2Gft(QObject *parent = 0); +}; + +#endif // O2GFT_H diff --git a/3rdparty/o2/src/o2globals.h b/3rdparty/o2/src/o2globals.h new file mode 100644 index 00000000..ca10e5e1 --- /dev/null +++ b/3rdparty/o2/src/o2globals.h @@ -0,0 +1,54 @@ +#ifndef O2GLOBALS_H +#define O2GLOBALS_H + +// Common constants +const char O2_ENCRYPTION_KEY[] = "12345678"; +const char O2_CALLBACK_URL[] = "http://127.0.0.1:%1/"; +const char O2_MIME_TYPE_XFORM[] = "application/x-www-form-urlencoded"; + +// QSettings key names +const char O2_KEY_TOKEN[] = "token.%1"; +const char O2_KEY_TOKEN_SECRET[] = "tokensecret.%1"; +const char O2_KEY_CODE[] = "code.%1"; +const char O2_KEY_EXPIRES[] = "expires.%1"; +const char O2_KEY_REFRESH_TOKEN[] = "refreshtoken.%1"; + +// 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_REDIRECT_URI[] = "redirect_uri"; +const char O2_OAUTH2_SCOPE[] = "scope"; +const char O2_OAUTH2_CODE[] = "code"; +const char O2_OAUTH2_TOKEN[] = "token"; +const char O2_OAUTH2_GRANT_TYPE[] = "grant_type"; +// OAuth 2 Response Parameters +const char O2_OAUTH2_ACCESS_TOKEN[] = "access_token"; +const char O2_OAUTH2_REFRESH_TOKEN[] = "refresh_token"; +const char O2_OAUTH2_EXPIRES_IN[] = "expires_in"; + +// OAuth signature types +const char O2_SIGNATURE_TYPE_HMAC_SHA1[] = "HMAC-SHA1"; +const char O2_SIGNATURE_TYPE_PLAINTEXT[] = "PLAINTEXT"; + +// Parameter values +const char O2_AUTHORIZATION_CODE[] = "authorization_code"; + +// Standard HTTP headers +const char O2_HTTP_AUTHORIZATION_HEADER[] = "Authorization"; + +#endif // O2GLOBALS_H diff --git a/3rdparty/o2/src/o2hubic.cpp b/3rdparty/o2/src/o2hubic.cpp new file mode 100644 index 00000000..5671cb9a --- /dev/null +++ b/3rdparty/o2/src/o2hubic.cpp @@ -0,0 +1,17 @@ +#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/3rdparty/o2/src/o2hubic.h b/3rdparty/o2/src/o2hubic.h new file mode 100644 index 00000000..33de56f3 --- /dev/null +++ b/3rdparty/o2/src/o2hubic.h @@ -0,0 +1,17 @@ +#ifndef O2HUBIC_H +#define O2HUBIC_H + +#include "o2.h" + +/// Hubic's dialect of OAuth 2.0 +class O2Hubic: public O2 { + Q_OBJECT + +public: + /// Constructor. + /// @param parent Parent object. + explicit O2Hubic(QObject *parent = 0); + +}; + +#endif // O2_HUBIC diff --git a/3rdparty/o2/src/o2reply.cpp b/3rdparty/o2/src/o2reply.cpp new file mode 100644 index 00000000..c0107e21 --- /dev/null +++ b/3rdparty/o2/src/o2reply.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "o2reply.h" + +O2Reply::O2Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) { + setSingleShot(true); + connect(this, SIGNAL(error(QNetworkReply::NetworkError)), reply, SIGNAL(error(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(this, SIGNAL(timeout()), this, SLOT(onTimeOut()), Qt::QueuedConnection); + start(timeOut); +} + +void O2Reply::onTimeOut() { + emit error(QNetworkReply::TimeoutError); +} + +O2ReplyList::~O2ReplyList() { + foreach (O2Reply *timedReply, replies_) { + delete timedReply; + } +} + +void O2ReplyList::add(QNetworkReply *reply) { + add(new O2Reply(reply)); +} + +void O2ReplyList::add(O2Reply *reply) { + replies_.append(reply); +} + +void O2ReplyList::remove(QNetworkReply *reply) { + O2Reply *o2Reply = find(reply); + if (o2Reply) { + o2Reply->stop(); + (void)replies_.removeOne(o2Reply); + } +} + +O2Reply *O2ReplyList::find(QNetworkReply *reply) { + foreach (O2Reply *timedReply, replies_) { + if (timedReply->reply == reply) { + return timedReply; + } + } + return 0; +} diff --git a/3rdparty/o2/src/o2reply.h b/3rdparty/o2/src/o2reply.h new file mode 100644 index 00000000..043741d5 --- /dev/null +++ b/3rdparty/o2/src/o2reply.h @@ -0,0 +1,53 @@ +#ifndef O2TIMEDREPLYLIST_H +#define O2TIMEDREPLYLIST_H + +#include +#include +#include +#include +#include +#include + +/// A network request/reply pair that can time out. +class O2Reply: public QTimer { + Q_OBJECT + +public: + O2Reply(QNetworkReply *reply, int timeOut = 60 * 1000, QObject *parent = 0); + +signals: + void error(QNetworkReply::NetworkError); + +public slots: + /// When time out occurs, the QNetworkReply's error() signal is triggered. + void onTimeOut(); + +public: + QNetworkReply *reply; +}; + +/// List of O2Replies. +class O2ReplyList { +public: + /// Destructor. + /// Deletes all O2Reply instances in the list. + virtual ~O2ReplyList(); + + /// Create a new O2Reply from a QNetworkReply, and add it to this list. + void add(QNetworkReply *reply); + + /// Add an O2Reply to the list, while taking ownership of it. + void add(O2Reply *reply); + + /// Remove item from the list that corresponds to a QNetworkReply. + void remove(QNetworkReply *reply); + + /// Find an O2Reply in the list, corresponding to a QNetworkReply. + /// @return Matching O2Reply or NULL. + O2Reply *find(QNetworkReply *reply); + +protected: + QList replies_; +}; + +#endif // O2TIMEDREPLYLIST_H diff --git a/3rdparty/o2/src/o2replyserver.cpp b/3rdparty/o2/src/o2replyserver.cpp new file mode 100644 index 00000000..90d3aef3 --- /dev/null +++ b/3rdparty/o2/src/o2replyserver.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2replyserver.h" + +#define trace() if (1) qDebug() +// #define trace() if (0) qDebug() + +O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent) { + connect(this, SIGNAL(newConnection()), this, SLOT(onIncomingConnection())); +} + +O2ReplyServer::~O2ReplyServer() { +} + +void O2ReplyServer::onIncomingConnection() { + QTcpSocket* socket = nextPendingConnection(); + connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection); + connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); +} + +void O2ReplyServer::onBytesReady() { + QTcpSocket *socket = qobject_cast(sender()); + if (!socket) { + return; + } + QByteArray reply; + QByteArray content; + content.append(""); + 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(content.size())); + reply.append(content); + socket->write(reply); + + QByteArray data = socket->readAll(); + QMap queryParams = parseQueryParams(&data); + socket->disconnectFromHost(); + close(); + emit verificationReceived(queryParams); +} + +QMap O2ReplyServer::parseQueryParams(QByteArray *data) { + trace() << "O2ReplyServer::parseQueryParams"; + + QString splitGetLine = QString(*data).split("\r\n").first(); + splitGetLine.remove("GET "); + splitGetLine.remove("HTTP/1.1"); + splitGetLine.remove("\r\n"); + splitGetLine.prepend("http://localhost"); + QUrl getTokenUrl(splitGetLine); + + QList< QPair > tokens; +#if QT_VERSION < 0x050000 + tokens = getTokenUrl.queryItems(); +#else + QUrlQuery query(getTokenUrl); + tokens = query.queryItems(); +#endif + QMultiMap queryParams; + QPair tokenPair; + foreach (tokenPair, tokens) { + // FIXME: We are decoding key and value again. This helps with Google OAuth, but is it mandated by the standard? + QString key = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.first.trimmed())); + QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed())); + queryParams.insert(key, value); + } + return queryParams; +} diff --git a/3rdparty/o2/src/o2replyserver.h b/3rdparty/o2/src/o2replyserver.h new file mode 100644 index 00000000..19b9932d --- /dev/null +++ b/3rdparty/o2/src/o2replyserver.h @@ -0,0 +1,26 @@ +#ifndef O2REPLYSERVER_H +#define O2REPLYSERVER_H + +#include +#include +#include +#include + +/// HTTP server to process authentication response. +class O2ReplyServer: public QTcpServer { + Q_OBJECT + +public: + explicit O2ReplyServer(QObject *parent = 0); + ~O2ReplyServer(); + +signals: + void verificationReceived(QMap); + +public slots: + void onIncomingConnection(); + void onBytesReady(); + QMap parseQueryParams(QByteArray *data); +}; + +#endif // O2REPLYSERVER_H diff --git a/3rdparty/o2/src/o2requestor.cpp b/3rdparty/o2/src/o2requestor.cpp new file mode 100644 index 00000000..4eb3047b --- /dev/null +++ b/3rdparty/o2/src/o2requestor.cpp @@ -0,0 +1,193 @@ +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2requestor.h" +#include "o2.h" +#include "o2globals.h" + +#define trace() if (1) qDebug() +// define trace() if (0) qDebug() + +O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent): QObject(parent), reply_(NULL), status_(Idle) { + manager_ = manager; + authenticator_ = authenticator; + qRegisterMetaType("QNetworkReply::NetworkError"); + connect(authenticator, SIGNAL(refreshFinished(QNetworkReply::NetworkError)), this, SLOT(onRefreshFinished(QNetworkReply::NetworkError)), Qt::QueuedConnection); +} + +O2Requestor::~O2Requestor() { +} + +int O2Requestor::get(const QNetworkRequest &req) { + if (-1 == setup(req, QNetworkAccessManager::GetOperation)) { + return -1; + } + reply_ = manager_->get(request_); + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + return id_; +} + +int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data) { + if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { + return -1; + } + data_ = data; + reply_ = manager_->post(request_, data_); + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data) { + if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { + return -1; + } + data_ = data; + reply_ = manager_->put(request_, data_); + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) { + if (status_ != Requesting) { + qWarning() << "O2Requestor::onRefreshFinished: No pending request"; + return; + } + if (QNetworkReply::NoError == error) { + QTimer::singleShot(100, this, SLOT(retry())); + } else { + error_ = error; + QTimer::singleShot(10, this, SLOT(finish())); + } +} + +void O2Requestor::onRequestFinished() { + QNetworkReply *senderReply = qobject_cast(sender()); + QNetworkReply::NetworkError error = senderReply->error(); + if (status_ == Idle) { + return; + } + if (reply_ != senderReply) { + return; + } + if (error == QNetworkReply::NoError) { + QTimer::singleShot(10, this, SLOT(finish())); + } +} + +void O2Requestor::onRequestError(QNetworkReply::NetworkError error) { + qWarning() << "O2Requestor::onRequestError: Error" << (int)error; + if (status_ == Idle) { + return; + } + if (reply_ != qobject_cast(sender())) { + return; + } + int httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + qWarning() << "O2Requestor::onRequestError: HTTP status" << httpStatus << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + trace() << reply_->readAll(); + if ((status_ == Requesting) && (httpStatus == 401)) { + // Call O2::refresh. Note the O2 instance might live in a different thread + if (QMetaObject::invokeMethod(authenticator_, "refresh")) { + return; + } + qCritical() << "O2Requestor::onRequestError: Invoking remote refresh failed"; + } + error_ = error; + QTimer::singleShot(10, this, SLOT(finish())); +} + +void O2Requestor::onUploadProgress(qint64 uploaded, qint64 total) { + if (status_ == Idle) { + qWarning() << "O2Requestor::onUploadProgress: No pending request"; + return; + } + if (reply_ != qobject_cast(sender())) { + return; + } + emit uploadProgress(id_, uploaded, total); +} + +int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation) { + static int currentId; + QUrl url; + + if (status_ != Idle) { + qWarning() << "O2Requestor::setup: Another request pending"; + return -1; + } + + request_ = req; + operation_ = operation; + id_ = currentId++; + url_ = url = req.url(); +#if QT_VERSION < 0x050000 + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); +#endif + request_.setUrl(url); + status_ = Requesting; + error_ = QNetworkReply::NoError; + return id_; +} + +void O2Requestor::finish() { + QByteArray data; + if (status_ == Idle) { + qWarning() << "O2Requestor::finish: No pending request"; + return; + } + data = reply_->readAll(); + status_ = Idle; + timedReplies_.remove(reply_); + reply_->disconnect(this); + reply_->deleteLater(); + emit finished(id_, error_, data); +} + +void O2Requestor::retry() { + if (status_ != Requesting) { + qWarning() << "O2Requestor::retry: No pending request"; + return; + } + timedReplies_.remove(reply_); + reply_->disconnect(this); + reply_->deleteLater(); + QUrl url = url_; +#if QT_VERSION < 0x050000 + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); +#endif + request_.setUrl(url); + status_ = ReRequesting; + switch (operation_) { + case QNetworkAccessManager::GetOperation: + reply_ = manager_->get(request_); + break; + case QNetworkAccessManager::PostOperation: + reply_ = manager_->post(request_, data_); + break; + default: + reply_ = manager_->put(request_, data_); + } + timedReplies_.add(reply_); + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); +} diff --git a/3rdparty/o2/src/o2requestor.h b/3rdparty/o2/src/o2requestor.h new file mode 100644 index 00000000..e356229c --- /dev/null +++ b/3rdparty/o2/src/o2requestor.h @@ -0,0 +1,82 @@ +#ifndef O2REQUESTOR_H +#define O2REQUESTOR_H + +#include +#include +#include +#include +#include +#include + +#include "o2reply.h" + +class O2; + +/// Makes authenticated requests. +class O2Requestor: public QObject { + Q_OBJECT + +public: + explicit O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent = 0); + ~O2Requestor(); + +public slots: + /// Make a GET request. + /// @return Request ID or -1 if there are too many requests in the queue. + int get(const QNetworkRequest &req); + + /// Make a POST request. + /// @return Request ID or -1 if there are too many requests in the queue. + int post(const QNetworkRequest &req, const QByteArray &data); + + /// Make a PUT request. + /// @return Request ID or -1 if there are too many requests in the queue. + int put(const QNetworkRequest &req, const QByteArray &data); + +signals: + /// Emitted when a request has been completed or failed. + void finished(int id, QNetworkReply::NetworkError error, QByteArray data); + + /// Emitted when an upload has progressed. + void uploadProgress(int id, qint64 bytesSent, qint64 bytesTotal); + +protected 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, emit finished() signal. + void finish(); + + /// Handle upload progress. + void onUploadProgress(qint64 uploaded, qint64 total); + +protected: + int setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation); + + enum Status { + Idle, Requesting, ReRequesting + }; + + QNetworkAccessManager *manager_; + O2 *authenticator_; + QNetworkRequest request_; + QByteArray data_; + QNetworkReply *reply_; + Status status_; + int id_; + QNetworkAccessManager::Operation operation_; + QUrl url_; + O2ReplyList timedReplies_; + QNetworkReply::NetworkError error_; +}; + +#endif // O2REQUESTOR_H diff --git a/3rdparty/o2/src/o2settingsstore.cpp b/3rdparty/o2/src/o2settingsstore.cpp new file mode 100644 index 00000000..a71e5b81 --- /dev/null +++ b/3rdparty/o2/src/o2settingsstore.cpp @@ -0,0 +1,47 @@ +#include +#include + +#include "o2settingsstore.h" + +static quint64 getHash(const QString &encryptionKey) { + return QCryptographicHash::hash(encryptionKey.toLatin1(), QCryptographicHash::Sha1).toULongLong(); +} + +O2SettingsStore::O2SettingsStore(const QString &encryptionKey, QObject *parent): + O2AbstractStore(parent), crypt_(getHash(encryptionKey)) { + settings_ = new QSettings(this); +} + +O2SettingsStore::O2SettingsStore(QSettings *settings, const QString &encryptionKey, QObject *parent): + O2AbstractStore(parent), crypt_(getHash(encryptionKey)) { + settings_ = settings; + settings_->setParent(this); +} + +O2SettingsStore::~O2SettingsStore() { +} + +QString O2SettingsStore::groupKey() const { + return groupKey_; +} + +void O2SettingsStore::setGroupKey(const QString &groupKey) { + if (groupKey_ == groupKey) { + return; + } + groupKey_ = groupKey; + emit groupKeyChanged(); +} + +QString O2SettingsStore::value(const QString &key, const QString &defaultValue) { + QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); + if (!settings_->contains(fullKey)) { + return defaultValue; + } + return crypt_.decryptToString(settings_->value(fullKey).toString()); +} + +void O2SettingsStore::setValue(const QString &key, const QString &value) { + QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); + settings_->setValue(fullKey, crypt_.encryptToString(value)); +} diff --git a/3rdparty/o2/src/o2settingsstore.h b/3rdparty/o2/src/o2settingsstore.h new file mode 100644 index 00000000..7c0c1efc --- /dev/null +++ b/3rdparty/o2/src/o2settingsstore.h @@ -0,0 +1,39 @@ +#ifndef O2SETTINGSSTORE_H +#define O2SETTINGSSTORE_H + +#include +#include + +#include "o2abstractstore.h" +#include "simplecrypt.h" + +class O2SettingsStore: public O2AbstractStore +{ + Q_OBJECT + +public: + + explicit O2SettingsStore(const QString &encryptionKey, QObject *parent = 0); + + explicit O2SettingsStore(QSettings *settings, const QString &encryptionKey, QObject *parent = 0); + + ~O2SettingsStore(); + + Q_PROPERTY(QString groupKey READ groupKey WRITE setGroupKey NOTIFY groupKeyChanged) + QString groupKey() const; + void setGroupKey(const QString &groupKey); + + QString value(const QString &key, const QString &defaultValue = QString()); + void setValue(const QString &key, const QString &value); + +signals: + // Property change signals + void groupKeyChanged(); + +protected: + QSettings* settings_; + QString groupKey_; + SimpleCrypt crypt_; +}; + +#endif // O2SETTINGSSTORE_H diff --git a/3rdparty/o2/src/o2skydrive.cpp b/3rdparty/o2/src/o2skydrive.cpp new file mode 100644 index 00000000..c8607d4c --- /dev/null +++ b/3rdparty/o2/src/o2skydrive.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2skydrive.h" +#include "o2globals.h" + +#define trace() if (1) qDebug() +// define trace() if (0) qDebug() + +O2Skydrive::O2Skydrive(QObject *parent): O2(parent) { + setRequestUrl("https://login.live.com/oauth20_authorize.srf"); + setTokenUrl("https://login.live.com/oauth20_token.srf"); + setRefreshTokenUrl("https://login.live.com/oauth20_token.srf"); +} + +void O2Skydrive::link() { + trace() << "O2::link"; + if (linked()) { + trace() << "Linked already"; + return; + } + + redirectUri_ = QString("https://login.live.com/oauth20_desktop.srf"); + + // Assemble intial authentication URL + QList > parameters; + parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE), (grantFlow_ == GrantFlowAuthorizationCode) ? QString(O2_OAUTH2_CODE) : QString(O2_OAUTH2_TOKEN))); + parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_)); + parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_)); + parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_)); + + // Show authentication URL with a web browser + QUrl url(requestUrl_); +#if QT_VERSION < 0x050000 + url.setQueryItems(parameters); +#else + QUrlQuery query(url); + query.setQueryItems(parameters); + url.setQuery(query); +#endif + emit openBrowser(url); +} + +void O2Skydrive::redirected(const QUrl &url) { + trace() << "O2::redirected" << url; + + emit closeBrowser(); + + if (grantFlow_ == GrantFlowAuthorizationCode) { + // Get access code + QString urlCode; +#if QT_VERSION < 0x050000 + urlCode = url.queryItemValue(O2_OAUTH2_CODE); +#else + QUrlQuery query(url); + urlCode = query.queryItemValue(O2_OAUTH2_CODE); +#endif + if (urlCode.isEmpty()) { + trace() << " Code not received"; + emit linkingFailed(); + return; + } + setCode(urlCode); + + // Exchange access code for access/refresh tokens + QNetworkRequest tokenRequest(tokenUrl_); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QMap parameters; + parameters.insert(O2_OAUTH2_CODE, code()); + parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_); + parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_); + parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE); + QByteArray data = buildRequestBody(parameters); + QNetworkReply *tokenReply = manager_->post(tokenRequest, data); + timedReplies_.add(tokenReply); + connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); + connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); + } else { + // Get access token + QString urlToken = ""; + QString urlRefreshToken = ""; + int urlExpiresIn = 0; + + QStringList parts = url.toString().split("#"); + if (parts.length() > 1) { + foreach (QString item, parts[1].split("&")) { + int index = item.indexOf("="); + if (index == -1) { + continue; + } + QString key = item.left(index); + QString value = item.mid(index + 1); + trace() << "" << key; + if (key == O2_OAUTH2_ACCESS_TOKEN) { + urlToken = value; + } else if (key == O2_OAUTH2_EXPIRES_IN) { + urlExpiresIn = value.toInt(); + } else if (key == O2_OAUTH2_REFRESH_TOKEN) { + urlRefreshToken = value; + } + } + } + + setToken(urlToken); + setRefreshToken(urlRefreshToken); + setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + urlExpiresIn); + if (urlToken.isEmpty()) { + emit linkingFailed(); + } else { + emit linkedChanged(); + emit linkingSucceeded(); + } + } +} diff --git a/3rdparty/o2/src/o2skydrive.h b/3rdparty/o2/src/o2skydrive.h new file mode 100644 index 00000000..879b057f --- /dev/null +++ b/3rdparty/o2/src/o2skydrive.h @@ -0,0 +1,18 @@ +#ifndef O2SKYDRIVE_H +#define O2SKYDRIVE_H + +#include "o2.h" + +/// Skydrive's dialect of OAuth 2.0 +class O2Skydrive: public O2 { + Q_OBJECT + +public: + explicit O2Skydrive(QObject *parent = 0); + +public slots: + Q_INVOKABLE void link(); + Q_INVOKABLE virtual void redirected(const QUrl &url); +}; + +#endif // O2SKYDRIVE_H diff --git a/3rdparty/o2/src/oxtwitter.cpp b/3rdparty/o2/src/oxtwitter.cpp new file mode 100644 index 00000000..45a540c5 --- /dev/null +++ b/3rdparty/o2/src/oxtwitter.cpp @@ -0,0 +1,71 @@ +#include +#include + +#include "oxtwitter.h" +#include "o2globals.h" + +#define trace() if (1) qDebug() + +const char XAUTH_USERNAME[] = "x_auth_username"; +const char XAUTH_PASSWORD[] = "x_auth_password"; +const char XAUTH_MODE[] = "x_auth_mode"; +const char XAUTH_MODE_VALUE[] = "client_auth"; + +OXTwitter::OXTwitter(QObject *parent): O1Twitter(parent) { +} + +QString OXTwitter::username() { + return username_; +} + +void OXTwitter::setUsername(const QString &username) { + username_ = username; + emit usernameChanged(); +} + +QString OXTwitter::password() { + return password_; +} + +void OXTwitter::setPassword(const QString &password) { + password_ = password; + emit passwordChanged(); +} + +void OXTwitter::link() { + trace() << "OXTwitter::link"; + if (linked()) { + trace() << "Linked already"; + return; + } + + if (username_.isEmpty() || password_.isEmpty()) { + qWarning() << "Error: XAuth parameters not set. Aborting!"; + return; + } + + // prepare XAuth parameters + xAuthParams_.append(O1RequestParameter(QByteArray(XAUTH_USERNAME), username_.toLatin1())); + xAuthParams_.append(O1RequestParameter(QByteArray(XAUTH_PASSWORD), password_.toLatin1())); + xAuthParams_.append(O1RequestParameter(QByteArray(XAUTH_MODE), QByteArray(XAUTH_MODE_VALUE))); + + QList oauthParams; + oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE_METHOD, O2_SIGNATURE_TYPE_HMAC_SHA1)); + oauthParams.append(O1RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_VERSION, "1.0")); + oauthParams.append(O1RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); + oauthParams.append(O1RequestParameter(O2_OAUTH_NONCE, nonce())); + oauthParams.append(O1RequestParameter(O2_OAUTH_TOKEN, QByteArray(""))); + oauthParams.append(O1RequestParameter(O2_OAUTH_VERFIER, QByteArray(""))); + + QByteArray signature = sign(oauthParams, xAuthParams_, accessTokenUrl(), QNetworkAccessManager::PostOperation, clientSecret(), ""); + oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE, signature)); + + // Post request + QNetworkRequest request(accessTokenUrl()); + request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); + request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); + QNetworkReply *reply = manager_->post(request, createQueryParams(xAuthParams_)); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); +} diff --git a/3rdparty/o2/src/oxtwitter.h b/3rdparty/o2/src/oxtwitter.h new file mode 100644 index 00000000..06f92679 --- /dev/null +++ b/3rdparty/o2/src/oxtwitter.h @@ -0,0 +1,36 @@ +#ifndef OXTWITTER_H +#define OXTWITTER_H + +#include "o1twitter.h" + +class OXTwitter: public O1Twitter { + Q_OBJECT + +public: + explicit OXTwitter(QObject *parent = 0); + /// Twitter XAuth login parameters + /// XAuth Username + Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged) + QString username(); + void setUsername(const QString &username); + + /// XAuth Password + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + QString password(); + void setPassword(const QString &username); + +public slots: + /// Authenticate. + Q_INVOKABLE virtual void link(); + +signals: + void usernameChanged(); + void passwordChanged(); + +private: + QList xAuthParams_; + QString username_; + QString password_; +}; + +#endif // OXTWITTER_H diff --git a/3rdparty/o2/src/simplecrypt.cpp b/3rdparty/o2/src/simplecrypt.cpp new file mode 100644 index 00000000..51b0e2bb --- /dev/null +++ b/3rdparty/o2/src/simplecrypt.cpp @@ -0,0 +1,254 @@ +/* +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 "simplecrypt.h" +#include +#include +#include +#include +#include +#include + +SimpleCrypt::SimpleCrypt(): + m_key(0), + m_compressionMode(CompressionAuto), + m_protectionMode(ProtectionChecksum), + m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +} + +SimpleCrypt::SimpleCrypt(quint64 key): + m_key(key), + m_compressionMode(CompressionAuto), + m_protectionMode(ProtectionChecksum), + m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); + splitKey(); +} + +void SimpleCrypt::setKey(quint64 key) +{ + m_key = key; + splitKey(); +} + +void SimpleCrypt::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 SimpleCrypt::encryptToByteArray(const QString& plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + return encryptToByteArray(plaintextArray); +} + +QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + + QByteArray ba = plaintext; + + CryptoFlags flags = CryptoFlagNone; + if (m_compressionMode == CompressionAlways) { + ba = qCompress(ba, 9); //maximum compression + flags |= CryptoFlagCompression; + } else if (m_compressionMode == CompressionAuto) { + QByteArray compressed = qCompress(ba, 9); + if (compressed.count() < ba.count()) { + ba = compressed; + flags |= CryptoFlagCompression; + } + } + + QByteArray integrityProtection; + if (m_protectionMode == ProtectionChecksum) { + flags |= CryptoFlagChecksum; + QDataStream s(&integrityProtection, QIODevice::WriteOnly); + s << qChecksum(ba.constData(), ba.length()); + } else if (m_protectionMode == ProtectionHash) { + flags |= CryptoFlagHash; + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + + integrityProtection += hash.result(); + } + + //prepend a random char to the string + char randomChar = char(qrand() & 0xFF); + ba = randomChar + integrityProtection + ba; + + int pos(0); + char lastChar(0); + + int cnt = ba.count(); + + while (pos < cnt) { + ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar; + lastChar = ba.at(pos); + ++pos; + } + + QByteArray resultArray; + resultArray.append(char(0x03)); //version for future updates to algorithm + resultArray.append(char(flags)); //encryption flags + resultArray.append(ba); + + m_lastError = ErrorNoError; + return resultArray; +} + +QString SimpleCrypt::encryptToString(const QString& plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + QByteArray cypher = encryptToByteArray(plaintextArray); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString SimpleCrypt::encryptToString(QByteArray plaintext) +{ + QByteArray cypher = encryptToByteArray(plaintext); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString SimpleCrypt::decryptToString(const QString &cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray plaintextArray = decryptToByteArray(cyphertextArray); + QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size()); + + return plaintext; +} + +QString SimpleCrypt::decryptToString(QByteArray cypher) +{ + QByteArray ba = decryptToByteArray(cypher); + QString plaintext = QString::fromUtf8(ba, ba.size()); + + return plaintext; +} + +QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray ba = decryptToByteArray(cyphertextArray); + + return ba; +} + +QByteArray SimpleCrypt::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/3rdparty/o2/src/simplecrypt.h b/3rdparty/o2/src/simplecrypt.h new file mode 100644 index 00000000..6dbe0806 --- /dev/null +++ b/3rdparty/o2/src/simplecrypt.h @@ -0,0 +1,225 @@ +/* +Copyright (c) 2011, Andre Somers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SIMPLECRYPT_H +#define SIMPLECRYPT_H +#include +#include +#include + +/** + @short Simple encryption and decryption of strings and byte arrays + + This class provides a simple implementation of encryption and decryption + of strings and byte arrays. + + @warning The encryption provided by this class is NOT strong encryption. It may + help to shield things from curious eyes, but it will NOT stand up to someone + determined to break the encryption. Don't say you were not warned. + + The class uses a 64 bit key. Simply create an instance of the class, set the key, + and use the encryptToString() method to calculate an encrypted version of the input string. + To decrypt that string again, use an instance of SimpleCrypt initialized with + the same key, and call the decryptToString() method with the encrypted string. If the key + matches, the decrypted version of the string will be returned again. + + If you do not provide a key, or if something else is wrong, the encryption and + decryption function will return an empty string or will return a string containing nonsense. + lastError() will return a value indicating if the method was succesful, and if not, why not. + + SimpleCrypt is prepared for the case that the encryption and decryption + algorithm is changed in a later version, by prepending a version identifier to the cypertext. + */ +class SimpleCrypt +{ +public: + /** + CompressionMode describes if compression will be applied to the data to be + encrypted. + */ + enum CompressionMode { + CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */ + CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */ + CompressionNever /*!< Never apply compression. */ + }; + /** + IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data + or wrong decryption keys. + + Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This + increases the length of the resulting cypertext, but makes it possible to check if the plaintext + appears to be valid after decryption. + */ + enum IntegrityProtectionMode { + ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */ + ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */ + ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */ + }; + /** + Error describes the type of error that occured. + */ + enum Error { + ErrorNoError, /*!< No error occurred. */ + ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */ + ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */ + ErrorIntegrityFailed /*!< The integrity check of the data failed. Perhaps the wrong key was used. */ + }; + + /** + Constructor. + + Constructs a SimpleCrypt instance without a valid key set on it. + */ + SimpleCrypt(); + /** + Constructor. + + Constructs a SimpleCrypt instance and initializes it with the given @arg key. + */ + explicit SimpleCrypt(quint64 key); + + /** + (Re-) initializes the key with the given @arg key. + */ + void setKey(quint64 key); + /** + Returns true if SimpleCrypt has been initialized with a key. + */ + bool hasKey() const {return !m_keyParts.isEmpty();} + + /** + Sets the compression mode to use when encrypting data. The default mode is Auto. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;} + /** + Returns the CompressionMode that is currently in use. + */ + CompressionMode compressionMode() const {return m_compressionMode;} + + /** + Sets the integrity mode to use when encrypting data. The default mode is Checksum. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;} + /** + Returns the IntegrityProtectionMode that is currently in use. + */ + IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;} + + /** + Returns the last error that occurred. + */ + Error lastError() const {return m_lastError;} + + /** + Encrypts the @arg plaintext string with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the string, so it can be stored easily in a text format. + */ + QString encryptToString(const QString& plaintext) ; + /** + Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the encryption, so it can be stored easily in a text format. + */ + QString encryptToString(QByteArray plaintext) ; + /** + Encrypts the @arg plaintext string with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(const QString& plaintext) ; + /** + Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(QByteArray plaintext) ; + + /** + Decrypts a cyphertext string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(const QString& cyphertext) ; + /** + Decrypts a cyphertext string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(const QString& cyphertext) ; + /** + Decrypts a cyphertext binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(QByteArray cypher) ; + /** + Decrypts a cyphertext binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(QByteArray cypher) ; + + //enum to describe options that have been used for the encryption. Currently only one, but + //that only leaves room for future extensions like adding a cryptographic hash... + enum CryptoFlag{CryptoFlagNone = 0, + CryptoFlagCompression = 0x01, + CryptoFlagChecksum = 0x02, + CryptoFlagHash = 0x04 + }; + Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag); +private: + + void splitKey(); + + quint64 m_key; + QVector m_keyParts; + CompressionMode m_compressionMode; + IntegrityProtectionMode m_protectionMode; + Error m_lastError; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags) + +#endif // SimpleCrypt_H diff --git a/3rdparty/o2/src/src.pri b/3rdparty/o2/src/src.pri new file mode 100644 index 00000000..29711abc --- /dev/null +++ b/3rdparty/o2/src/src.pri @@ -0,0 +1,36 @@ +QT *= network script + +INCLUDEPATH += $$PWD +SOURCES += \ + $$PWD/o1.cpp \ + $$PWD/o1requestor.cpp \ + $$PWD/o2.cpp \ + $$PWD/o2facebook.cpp \ + $$PWD/o2gft.cpp \ + $$PWD/o2reply.cpp \ + $$PWD/o2replyserver.cpp \ + $$PWD/o2requestor.cpp \ + $$PWD/simplecrypt.cpp \ + $$PWD/o2skydrive.cpp \ + $$PWD/oxtwitter.cpp \ + $$PWD/o2settingsstore.cpp + +HEADERS += \ + $$PWD/o1.h \ + $$PWD/o1dropbox.h \ + $$PWD/o1flickr.h \ + $$PWD/o1requestor.h \ + $$PWD/o1twitter.h \ + $$PWD/o2.h \ + $$PWD/o2facebook.h \ + $$PWD/o2gft.h \ + $$PWD/o2reply.h \ + $$PWD/o2replyserver.h \ + $$PWD/o2requestor.h \ + $$PWD/simplecrypt.h \ + $$PWD/o2skydrive.h \ + $$PWD/o2globals.h \ + $$PWD/oxtwitter.h \ + $$PWD/o2abstractstore.h \ + $$PWD/o2settingsstore.h \ + $$PWD/o1freshbooks.h diff --git a/CMakeLists.txt b/CMakeLists.txt index efcb79a0..280a32bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -460,6 +460,7 @@ ADD_SUBDIRECTORY(plugin) ADD_SUBDIRECTORY(include) ADD_SUBDIRECTORY(spectrum) ADD_SUBDIRECTORY(backends) +ADD_SUBDIRECTORY(3rdparty) if (NOT WIN32) ADD_SUBDIRECTORY(spectrum_manager) # ADD_SUBDIRECTORY(spectrum2_send_message)