diff --git a/external/o2/README.md b/external/o2/README.md index 24a80d8710d..f7ffd47903f 100644 --- a/external/o2/README.md +++ b/external/o2/README.md @@ -4,9 +4,11 @@ This library encapsulates the OAuth 1.0 and 2.0 client authentication flows, and The primary target is Qt Quick applications on embedded devices. +Supported Qt versions: 5 and 6. + Notes to contributors: - * Please follow the coding style of the existing source + * Please follow the coding style of the existing source code * Code contributions are released under Simplified BSD License, as specified in LICENSE. Do not contribute if this license does not suit your code ## Classes @@ -25,17 +27,21 @@ O1Freshbooks | o1freshbooks.h | Freshbooks OAuth specialization O1Requestor | o1requestor.h | Makes authenticated OAuth 1.0 requests: GET, POST or PUT, handles timeouts O1RequestParameter | o1.h | An extra request parameter participating in request signing O1Twitter | o1twitter.h | Twitter OAuth specialization +O1SmugMug | o1smugmug.h | SmugMug OAuth specialization O2 | o2.h | Generic OAuth 2.0 authenticator O2Facebook | o2facebook.h | Facebook OAuth specialization O2Gft | o2gft.h | Google Fusion Tables OAuth specialization O2Google | o2google.h | Google Oauth specialization [scopes](https://developers.google.com/identity/protocols/googlescopes) +O2GoogleDevice | o2google.h | Google [Sign-In for TVs and Devices](https://developers.google.com/identity/sign-in/devices) O2Hubic | o2hubic.h | Hubic OAuth specialization +O2Msgraph | o2msgraph.h | Microsoft Graph OAuth specialization O2Reply | o2reply.h | A network request/reply that can time out O2ReplyServer | o2replyserver.h | HTTP server to process authentication responses O2Requestor | o2requestor.h | Makes authenticated OAuth 2.0 requests (GET, POST or PUT), handles timeouts and token expiry O2Skydrive | o2skydrive.h | OneDrive OAuth specialization O2SurveyMonkey | o2surveymonkey.h | SurveyMonkey OAuth specialization OXTwitter | oxtwitter.h | Twitter XAuth specialization +O2Uber | o2uber.h | Uber OAuth specialization ## Installation @@ -49,24 +55,28 @@ This example assumes a hypothetical Twitter client that will post tweets. Twitte 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; +```c++ +#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())); +```c++ +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()*. +**Note:** For browserless Twitter authentication, you can use the OXTwitter specialized class that can do Twitter XAuth. You will need to additionally provide your Twitter login credentials (username & password) before calling *link()*. ### Handling Signals @@ -74,35 +84,46 @@ O2 is an asynchronous library. It will send signals at various stages of authent 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 - } +```c++ +void onLinkedChanged() { + // Linking (login) state has changed. + // Use o1->linked() to get the actual state +} - void onLinkingFailed() { - // Login has failed - } +void onLinkingFailed() { + // Login has failed +} - void onLinkingSucceeded() { - // Login has succeeded - } +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 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() +} +``` + +**Note:** From _onOpenBrowser_, prefer opening a web view, instead of a full-blown external browser. + +**Note:** If you _must_ use an external browser on Android, change this line in the Qt-generated manifest.xml: + +```meta-data android:name="android.app.background_running" android:value="true"``` - void onCloseBrowser() { - // Close the browser window opened in openBrowser() - } ### Logging In To log in (e.g. to link your application to the OAuth service), call the link() method: - o1->link(); +```c++ +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. @@ -110,7 +131,9 @@ This initiates the authentication sequence. Your signal handlers above will be c To log out, call the unlink() method: - o1->unlink(); +```c++ +o1->unlink(); +``` Logging out always succeeds, and requires no user interaction. @@ -120,86 +143,108 @@ Once linked, you can start sending authenticated requests to the service. We sta First we need a Qt network manager and an O1 requestor object: - QNetworkAccessManager *manager = new QNetworkAccessManager(this); - O1Requestor *requestor = new O1Requestor(manager, o1, this); +```c++ +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!"); +```c++ +QByteArray paramName("status"); +QByteArray tweetText("My first tweet!"); - QList requestParams = QList(); - requestParams << O1RequestParameter(paramName, tweetText); +QList requestParams = QList(); +requestParams << O1RequestParameter(paramName, tweetText); - QByteArray postData = O1::createQueryParams(requestParams); +QByteArray postData = O1::createQueryParams(requestParams); - // Using Twitter's REST API ver 1.1 - QUrl url = QUrl("https://api.twitter.com/1.1/statuses/update.json"); +// 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); +QNetworkRequest request(url); +request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); +``` Finally we authenticate and send the request using the O1 requestor object: - QNetworkReply *reply = requestor->post(request, reqestParams, postData); +```c++ +QNetworkReply *reply = requestor->post(request, reqestParams, postData); +``` Continuing with the example, we will now send a tweet containing an image as well as a message. We create an HTTP request containing the image and the message, in the format specified by Twitter: - QString imagePath("/tmp/image.jpg"); - QString message("My tweet with an image!"); +```c++ +QString imagePath("/tmp/image.jpg"); +QString message("My tweet with an image!"); - QFileInfo fileInfo(imagePath); - QFile file(imagePath); - file.open(QIODevice::ReadOnly); +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"; +QString boundary("7d44e178b0439"); +QByteArray data(QString("--" + boundary + "\r\n").toAscii()); +data += "Content-Disposition: form-data; name=\"media[]\"; filename=\"" + fileInfo.fileName() + "\"\r\n"; +data += "Content-Transfer-Encoding: binary\r\n"; +data += "Content-Type: application/octet-stream\r\n\r\n"; +data += file.readAll(); +file.close(); +data += QString("\r\n--") + boundary + "\r\n"; +data += "Content-Disposition: form-data; name=\"status\"\r\n"; +data += "Content-Transfer-Encoding: binary\r\n"; +data += "Content-Type: text/plain; charset=utf-8\r\n\r\n"; +data += message.toUtf8(); +data += QString("\r\n--") + boundary + "--\r\n"; - QNetworkRequest request; - // Using Twitter's REST API ver 1.1 - request.setUrl(QUrl("https://api.twitter.com/1.1/statuses/update_with_media.json")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); - request.setHeader(QNetworkRequest::ContentLengthHeader, data.length()); +QNetworkRequest request; +// Using Twitter's REST API ver 1.1 +request.setUrl(QUrl("https://api.twitter.com/1.1/statuses/update_with_media.json")); +request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); +request.setHeader(QNetworkRequest::ContentLengthHeader, data.length()); - QNetworkReply *reply = requestor->post(request, QList(), data); +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 persistent 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: +O2 provides simple storage classes for writing OAuth tokens in a peristent location. Currently, a QSettings based backing store **O0SettingsStore** is provided in O2. O2SettingsStore keeps all token values in an encrypted form. You have to specify the encryption key to use while constructing the object: - O0SettingsStore settings = new O0SettingsStore("myencryptionkey"); - // Set the store before starting OAuth, i.e before calling link() - o1->setStore(settings); - ... +```c++ +O0SettingsStore *settings = new O0SettingsStore("myencryptionkey"); +// Set the store before starting OAuth, i.e before calling link() +o1->setStore(settings); +// ... +``` + +Once set, the O0BaseAuth takes ownership of the O0SettingsStore object. You can also create it with your customized QSettings object. O2SettingsStore will then use that QSettings object for storing the tokens: - O0SettingsStore settings = new O0SettingsStore(mySettingsObject, "myencryptionkey"); +```c++ +O0SettingsStore *settings = new O0SettingsStore(mySettingsObject, "myencryptionkey"); +``` Once set, O2SettingsStore takes ownership of the QSettings object. -**Note:** If you do not specify a storage object to use, O2 will create one by default (which QSettings based), and use it. In such a case, a default encryption key is used for encrypting the data. +**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. *This is not a secure solution: prefer storing the tokens in a Keychain or Wallet based facility instead*. + +**Note:** If using O2SettingsStore, make sure organization name, domain and application name are set: + +```c++ +QCoreApplication::setOrganizationName("MySoft"); +QCoreApplication::setOrganizationDomain("mysoft.com"); +QCoreApplication::setApplicationName("Star Runner"); +``` ### 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*. +Some OAuth services provide additional information in the access token response. For example Twitter returns two additional tokens: *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. +O2 exposes such tokens via the property *extraTokens*. You can query this property after a successful OAuth exchange, i.e after the *linkingSucceeded()* signal has been emitted. ## More Examples @@ -213,11 +258,11 @@ twitterdemo | Command line client for authenticating with Twitter and posting st ## Change Log +### 1.0.2 + +* Last Qt5-only release + ### 0.1.0 * Persist the extra tokens, too * Add Qt Quick Twitter client example - - - - diff --git a/external/o2/README_QGIS.txt b/external/o2/README_QGIS.txt index 84151050152..eda68f4a0f9 100644 --- a/external/o2/README_QGIS.txt +++ b/external/o2/README_QGIS.txt @@ -1,4 +1,2 @@ -O2 Library from https://github.com/pipacs/o2/archive/31ceafb3f0c3b605110ddd20aeebd3288504ee1f.tar.gz - -Note: current master build of O2 is broken if built with keychain support +O2 Library from https://github.com/pipacs/o2/archive/99902cc37e083a8311c1f8eee918e93c93cbc937.tar.gz diff --git a/external/o2/o2.pc.cmake b/external/o2/o2.pc.cmake index 27955ed5cdf..277951880a9 100644 --- a/external/o2/o2.pc.cmake +++ b/external/o2/o2.pc.cmake @@ -7,4 +7,4 @@ Description: OAuth 2.0 for Qt Version: @PROJECT_VERSION@ Cflags: -I${includedir} @CMAKE_INCLUDE_PATH@ -Libs: -L${libdir} @CMAKE_LIBRARY_PATH@ +Libs: -L${libdir} -lo2 diff --git a/external/o2/src/CMakeLists.txt b/external/o2/src/CMakeLists.txt index 563a9055c57..39536cb71e5 100644 --- a/external/o2/src/CMakeLists.txt +++ b/external/o2/src/CMakeLists.txt @@ -20,16 +20,19 @@ endif(NOT o2_WITH_QT5) set( o2_SRCS o2.cpp + o2pollserver.cpp o2reply.cpp o2replyserver.cpp o2requestor.cpp o2simplecrypt.cpp + o0jsonresponse.cpp o0settingsstore.cpp o0baseauth.cpp ) set( o2_HDRS o2.h + o2pollserver.h o2reply.h o2replyserver.h o2requestor.h @@ -37,6 +40,7 @@ set( o2_HDRS o0baseauth.h o0export.h o0globals.h + o0jsonresponse.h o0requestparameter.h o0settingsstore.h o0simplecrypt.h @@ -82,14 +86,27 @@ if(o2_WITH_GOOGLE) ${o2_SRCS} o2gft.cpp o2google.cpp + o2googledevice.cpp ) set( o2_HDRS ${o2_HDRS} o2gft.h o2google.h + o2googledevice.h ) endif(o2_WITH_GOOGLE) +if(o2_WITH_VIMEO) + set( o2_SRCS + ${o2_SRCS} + o2vimeo.cpp + ) + set( o2_HDRS + ${o2_HDRS} + o2vimeo.h + ) +endif(o2_WITH_VIMEO) + if(o2_WITH_FACEBOOK) set( o2_SRCS ${o2_SRCS} @@ -152,6 +169,27 @@ if(o2_WITH_SURVEYMONKEY) ) endif(o2_WITH_SURVEYMONKEY) +if(o2_WITH_SMUGMUG) + set( o2_SRCS + ${o2_SRCS} + o1smugmug.cpp + ) + set( o2_HDRS + ${o2_HDRS} + o1smugmug.h + ) +endif(o2_WITH_SMUGMUG) + +if(o2_WITH_MSGRAPH) + set( o2_SRCS + ${o2_SRCS} + o2msgraph.cpp + ) + set( o2_HDRS + ${o2_HDRS} + o2msgraph.h + ) +endif(o2_WITH_MSGRAPH) if(o2_WITH_KEYCHAIN) if (Qt5Core_DIR) @@ -160,7 +198,7 @@ if(o2_WITH_KEYCHAIN) find_package(QtKeychain REQUIRED) endif() if(QTKEYCHAIN_FOUND OR QT5KEYCHAIN_FOUND) - message("Found QTKeychain") + MESSAGE("Found QTKeychain") list(APPEND LINK_TARGETS ${QTKEYCHAIN_LIBRARY}) include_directories(${QTKEYCHAIN_INCLUDE_DIR}) set( o2_SRCS @@ -172,7 +210,7 @@ if(o2_WITH_KEYCHAIN) o0keychainstore.h ) else() - message("Qt5Keychain or QtKeychain is required") + MESSAGE("Qt5Keychain or QtKeychain is required") endif() @@ -229,5 +267,9 @@ install(FILES ${o2_HDRS} ) message(STATUS "Writing pkg-config file...") -configure_file(${CMAKE_SOURCE_DIR}/o2.pc.cmake ${CMAKE_BINARY_DIR}/o2.pc @ONLY) -install(FILES ${CMAKE_BINARY_DIR}/o2.pc DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${o2_LIB_SUFFIX}/pkgconfig/") +configure_file(${CMAKE_CURRENT_LIST_DIR}/../o2.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/o2.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/o2.pc DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${o2_LIB_SUFFIX}/pkgconfig/") + +configure_file(${CMAKE_CURRENT_LIST_DIR}/../o2-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/o2-config.h @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/o2-config.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include/o2") + diff --git a/external/o2/src/o0baseauth.cpp b/external/o2/src/o0baseauth.cpp index 319b35b836e..d9d12d9c53e 100644 --- a/external/o2/src/o0baseauth.cpp +++ b/external/o2/src/o0baseauth.cpp @@ -1,16 +1,19 @@ #include #include #include +#include #include "o0baseauth.h" #include "o0globals.h" #include "o0settingsstore.h" +#include "o2replyserver.h" +#include "o2pollserver.h" static const quint16 DefaultLocalPort = 1965; -O0BaseAuth::O0BaseAuth(QObject *parent): QObject(parent) { +O0BaseAuth::O0BaseAuth(QObject *parent, O0AbstractStore *store): QObject(parent), store_(0), useExternalWebInterceptor_(false), replyServer_(NULL), pollServer_(NULL) { localPort_ = DefaultLocalPort; - store_ = new O0SettingsStore(O2_ENCRYPTION_KEY, this); + setStore(store); } void O0BaseAuth::setStore(O0AbstractStore *store) { @@ -83,6 +86,25 @@ void O0BaseAuth::setClientSecret(const QString &value) { Q_EMIT clientSecretChanged(); } +bool O0BaseAuth::useExternalWebInterceptor() { + return useExternalWebInterceptor_; +} + +void O0BaseAuth::setUseExternalWebInterceptor(bool useExternalWebInterceptor) { + useExternalWebInterceptor_ = useExternalWebInterceptor; +} + +QByteArray O0BaseAuth::replyContent() const { + return replyContent_; +} + +void O0BaseAuth::setReplyContent(const QByteArray &value) { + replyContent_ = value; + if (replyServer_) { + replyServer_->setReplyContent(replyContent_); + } +} + int O0BaseAuth::localPort() { return localPort_; } @@ -112,6 +134,32 @@ void O0BaseAuth::setExtraTokens(QVariantMap extraTokens) { Q_EMIT extraTokensChanged(); } +void O0BaseAuth::setReplyServer(O2ReplyServer * server) +{ + delete replyServer_; + + replyServer_ = server; + replyServer_->setReplyContent(replyContent_); +} + +O2ReplyServer * O0BaseAuth::replyServer() const +{ + return replyServer_; +} + +void O0BaseAuth::setPollServer(O2PollServer *server) +{ + if (pollServer_) + pollServer_->deleteLater(); + + pollServer_ = server; +} + +O2PollServer *O0BaseAuth::pollServer() const +{ + return pollServer_; +} + QByteArray O0BaseAuth::createQueryParameters(const QList ¶meters) { QByteArray ret; bool first = true; diff --git a/external/o2/src/o0baseauth.h b/external/o2/src/o0baseauth.h index e015dc365d3..743c4c11eab 100644 --- a/external/o2/src/o0baseauth.h +++ b/external/o2/src/o0baseauth.h @@ -12,12 +12,15 @@ #include "o0abstractstore.h" #include "o0requestparameter.h" -/// Base class of OAuth authenticators -class O0_EXPORT O0BaseAuth : public QObject { - Q_OBJECT +class O2ReplyServer; +class O2PollServer; +/// Base class of OAuth authenticators +class O0_EXPORT O0BaseAuth : public QObject +{ + Q_OBJECT public: - explicit O0BaseAuth(QObject *parent = 0); + explicit O0BaseAuth(QObject *parent = 0, O0AbstractStore *store = 0); public: /// Are we authenticated? @@ -48,6 +51,17 @@ public: QString clientSecret(); void setClientSecret(const QString &value); + /// Should we use a reply server (default) or an external web interceptor? + Q_PROPERTY(bool useExternalWebInterceptor READ useExternalWebInterceptor WRITE setUseExternalWebInterceptor) + bool useExternalWebInterceptor(); + void setUseExternalWebInterceptor(bool inUseExternalWebInterceptor); + + /// Page content on local host after successful oauth. + /// Provide it in case you do not want to close the browser, but display something + Q_PROPERTY(QByteArray replyContent READ replyContent WRITE setReplyContent) + QByteArray replyContent() const; + void setReplyContent(const QByteArray &value); + /// TCP port number to use in local redirections. /// The OAuth "redirect_uri" will be set to "http://localhost:/". /// If localPort is set to 0 (default), O2 will replace it with a free one. @@ -75,6 +89,9 @@ Q_SIGNALS: /// Emitted when client can close the browser window. void closeBrowser(); + /// Emitted when client needs to show a verification uri and user code + void showVerificationUriAndCode(const QUrl &uri, const QString &code); + /// Emitted when authentication/deauthentication succeeded. void linkingSucceeded(); @@ -104,6 +121,16 @@ protected: /// Set extra tokens found in OAuth response void setExtraTokens(QVariantMap extraTokens); + /// Set local reply server + void setReplyServer(O2ReplyServer *server); + + O2ReplyServer * replyServer() const; + + /// Set local poll server + void setPollServer(O2PollServer *server); + + O2PollServer * pollServer() const; + protected: QString clientId_; QString clientSecret_; @@ -118,6 +145,12 @@ protected: QVariantMap extraTokens_; QByteArray pkceCodeVerifier_; QString pkceCodeChallenge_; + bool useExternalWebInterceptor_; + QByteArray replyContent_; + +private: + O2ReplyServer *replyServer_; + O2PollServer *pollServer_; }; #endif // O0BASEAUTH diff --git a/external/o2/src/o0globals.h b/external/o2/src/o0globals.h index f98d9293714..f626adcb481 100644 --- a/external/o2/src/o0globals.h +++ b/external/o2/src/o0globals.h @@ -24,6 +24,7 @@ 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"; @@ -41,22 +42,33 @@ const char O2_OAUTH2_SCOPE[] = "scope"; const char O2_OAUTH2_GRANT_TYPE_CODE[] = "code"; const char O2_OAUTH2_GRANT_TYPE_TOKEN[] = "token"; const char O2_OAUTH2_GRANT_TYPE_PASSWORD[] = "password"; +const char O2_OAUTH2_GRANT_TYPE_DEVICE[] = "urn:ietf:params:oauth:grant-type:device_code"; const char O2_OAUTH2_GRANT_TYPE[] = "grant_type"; const char O2_OAUTH2_API_KEY[] = "api_key"; +const char O2_OAUTH2_STATE[] = "state"; +const char O2_OAUTH2_CODE[] = "code"; // 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"; +const char O2_OAUTH2_DEVICE_CODE[] = "device_code"; +const char O2_OAUTH2_USER_CODE[] = "user_code"; +const char O2_OAUTH2_VERIFICATION_URI[] = "verification_uri"; +const char O2_OAUTH2_VERIFICATION_URL[] = "verification_url"; // Google sign-in +const char O2_OAUTH2_VERIFICATION_URI_COMPLETE[] = "verification_uri_complete"; +const char O2_OAUTH2_INTERVAL[] = "interval"; // OAuth signature types const char O2_SIGNATURE_TYPE_HMAC_SHA1[] = "HMAC-SHA1"; +const char O2_SIGNATURE_TYPE_HMAC_SHA256[] = "HMAC-SHA256"; const char O2_SIGNATURE_TYPE_PLAINTEXT[] = "PLAINTEXT"; // Parameter values const char O2_AUTHORIZATION_CODE[] = "authorization_code"; // Standard HTTP headers +const char O2_HTTP_HTTP_HEADER[] = "HTTP"; const char O2_HTTP_AUTHORIZATION_HEADER[] = "Authorization"; // PKCE parameters diff --git a/external/o2/src/o0jsonresponse.cpp b/external/o2/src/o0jsonresponse.cpp new file mode 100644 index 00000000000..b07c6a94a5f --- /dev/null +++ b/external/o2/src/o0jsonresponse.cpp @@ -0,0 +1,41 @@ +#include "o0jsonresponse.h" + +#include +#include +#if QT_VERSION >= 0x050000 +#include +#include +#else +#include +#include +#endif + +QVariantMap parseJsonResponse(const QByteArray &data) { +#if QT_VERSION >= 0x050000 + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) { + qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString(); + return QVariantMap(); + } + + if (!doc.isObject()) { + qWarning() << "parseTokenResponse: Token response is not an object"; + return QVariantMap(); + } + + return doc.object().toVariantMap(); +#else + QScriptEngine engine; + QScriptValue value = engine.evaluate("(" + QString(data) + ")"); + QScriptValueIterator it(value); + QVariantMap map; + + while (it.hasNext()) { + it.next(); + map.insert(it.name(), it.value().toVariant()); + } + + return map; +#endif +} diff --git a/external/o2/src/o0jsonresponse.h b/external/o2/src/o0jsonresponse.h new file mode 100644 index 00000000000..bdcb286f751 --- /dev/null +++ b/external/o2/src/o0jsonresponse.h @@ -0,0 +1,11 @@ +#ifndef O0JSONRESPONSE_H +#define O0JSONRESPONSE_H + +#include + +class QByteArray; + +/// Parse JSON data into a QVariantMap +QVariantMap parseJsonResponse(const QByteArray &data); + +#endif // O0JSONRESPONSE_H diff --git a/external/o2/src/o0keychainstore.cpp b/external/o2/src/o0keychainstore.cpp index f9991e3fa42..88ac9252440 100644 --- a/external/o2/src/o0keychainstore.cpp +++ b/external/o2/src/o0keychainstore.cpp @@ -21,62 +21,63 @@ o0keyChainStore::o0keyChainStore(const QString& app,const QString& name,QObject } QString o0keyChainStore::value(const QString &key, const QString &defaultValue) { - Q_UNUSED(defaultValue) - return pairs_.value(key); + return pairs_.value(key, defaultValue); } void o0keyChainStore::setValue(const QString &key, const QString &value) { pairs_.insert(key,value); } -void o0keyChainStore::persist() { +int o0keyChainStore::persist() { WritePasswordJob job(app_); - job.setAutoDelete(false); - job.setKey(name_); + initJob(job); + QByteArray data; QDataStream ds(&data,QIODevice::ReadWrite); ds << pairs_; + job.setBinaryData(data); - job.setTextData(data); - QEventLoop loop; - job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); - job.start(); - loop.exec(); - if(job.error()) - { - qWarning() << "keychain could not be persisted "<< name_ << ":" << qPrintable(job.errorString()); - } + return executeJob(job, "persist"); } -void o0keyChainStore::fetchFromKeychain() { +int o0keyChainStore::fetchFromKeychain() { ReadPasswordJob job(app_); - job.setKey(name_); - QEventLoop loop; - job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); - job.start(); - loop.exec(); - - QByteArray data; - // QKeychain::ReadPasswordJob::textData() returns QString::fromUtf8( ) - // switch back to UTF-8 to avoid issues when QT_NO_CAST_TO_ASCII is defined - data.append(job.textData().toUtf8()); - QDataStream ds(&data,QIODevice::ReadOnly); - ds >> pairs_; - - if(job.error()) - { - qWarning() << "keychain could not be fetched"<< name_ << ":" << qPrintable(job.errorString()); + initJob(job); + const int result = executeJob(job, "fetch"); + if (result == 0) { // success + QByteArray data; + data.append(job.binaryData()); + QDataStream ds(&data, QIODevice::ReadOnly); + ds >> pairs_; } + return result; } -void o0keyChainStore::clearFromKeychain() { +int o0keyChainStore::clearFromKeychain() { DeletePasswordJob job(app_); + initJob(job); + return executeJob(job, "clear"); +} + +bool o0keyChainStore::isEntryNotFoundError(int errorCode) { + return errorCode == QKeychain::EntryNotFound; +} + +void o0keyChainStore::initJob(QKeychain::Job &job) const { + job.setAutoDelete(false); job.setKey(name_); +} + +int o0keyChainStore::executeJob(QKeychain::Job &job, const char *actionName) const { QEventLoop loop; job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); job.start(); loop.exec(); - if ( job.error() ) { - qWarning() << "Deleting keychain failed: " << qPrintable(job.errorString()); + + const QKeychain::Error errorCode = job.error(); + if (errorCode != QKeychain::NoError) { + qWarning() << "keychain store could not" << actionName << name_ << ":" + << job.errorString() << "(" << errorCode << ")."; } + return errorCode; } diff --git a/external/o2/src/o0keychainstore.h b/external/o2/src/o0keychainstore.h index 2afc3b26444..b915f4f1f0a 100644 --- a/external/o2/src/o0keychainstore.h +++ b/external/o2/src/o0keychainstore.h @@ -8,21 +8,48 @@ #include "o0abstractstore.h" #include +namespace QKeychain { +class Job; +} + +/// Calling persist(), fetchFromKeychain() and clearFromKeychain() member +/// functions is the responsibility of the user of this class. +/// This is important to minimize the number of keychain accesses (and +/// potentially the number of user password prompts). +/// For example: fetchFromKeychain() can be called immediately after +/// creating a keychain store; persist() - after a successful authorization; +/// clearFromKeychain() - when the user logs out from the service. class O0_EXPORT o0keyChainStore : public O0AbstractStore{ Q_OBJECT public: explicit o0keyChainStore(const QString& app,const QString& name,QObject *parent = 0); /// Retrieve a string value by key. - virtual QString value(const QString &key, const QString &defaultValue = QString()) = 0; + QString value(const QString &key, const QString &defaultValue = QString()); /// Set a string value for a key. - virtual void setValue(const QString &key, const QString &value) = 0; + void setValue(const QString &key, const QString &value); + + // The functions below return QKeychain::Error casted to int. They don't + // return the enumerator directly because it can not be forward-declared reliably, + // and including into this header may be undesirable. + // Note that if 0 is returned, then there was no error. + + int persist(); + int fetchFromKeychain(); + int clearFromKeychain(); + + /// @return true if @p errorCode is equal to QKeychain::EntryNotFound. + /// @note This function can be used to single out one type of an error + /// returned from the functions above without including . + /// The EntryNotFound error type is special because it can be considered + /// not an error if returned from clearFromKeychain(). + static bool isEntryNotFoundError(int errorCode); - void persist(); - void fetchFromKeychain(); - void clearFromKeychain(); private: + void initJob(QKeychain::Job &job) const; + int executeJob(QKeychain::Job &job, const char *actionName) const; + QString app_; QString name_; QMap pairs_; diff --git a/external/o2/src/o0requestparameter.h b/external/o2/src/o0requestparameter.h index 04fed95f1d9..4e234f7e9d5 100644 --- a/external/o2/src/o0requestparameter.h +++ b/external/o2/src/o0requestparameter.h @@ -3,6 +3,8 @@ #include "o0export.h" +#include + /// Request parameter (name-value pair) participating in authentication. struct O0_EXPORT O0RequestParameter { O0RequestParameter(const QByteArray &n, const QByteArray &v): name(n), value(v) {} diff --git a/external/o2/src/o0settingsstore.cpp b/external/o2/src/o0settingsstore.cpp index c475389532b..72ed97a37be 100644 --- a/external/o2/src/o0settingsstore.cpp +++ b/external/o2/src/o0settingsstore.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "o0settingsstore.h" @@ -41,4 +42,13 @@ QString O0SettingsStore::value(const QString &key, const QString &defaultValue) void O0SettingsStore::setValue(const QString &key, const QString &value) { QString fullKey = groupKey_.isEmpty() ? key : (groupKey_ + '/' + key); settings_->setValue(fullKey, crypt_.encryptToString(value)); + + const QSettings::Status status = settings_->status(); + if (status != QSettings::NoError) { + qCritical() << "O0SettingsStore QSettings error:" << status; + if (status == QSettings::AccessError) { + qCritical() << "Did you forget to set organization name and application name " + "in QSettings or QCoreApplication?"; + } + } } diff --git a/external/o2/src/o0simplecrypt.h b/external/o2/src/o0simplecrypt.h index b3faf4b2604..8b180791866 100644 --- a/external/o2/src/o0simplecrypt.h +++ b/external/o2/src/o0simplecrypt.h @@ -30,7 +30,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) +#include +#endif #include "o0baseauth.h" /** @@ -82,7 +84,7 @@ public: ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */ }; /** - Error describes the type of error that occurred. + Error describes the type of error that occured. */ enum Error { ErrorNoError, /*!< No error occurred. */ @@ -175,7 +177,7 @@ public: Decrypts a cyphertext string encrypted with this class with the set key back to the plain text version. - If an error occurred, such as non-matching keys between encryption and decryption, + 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) ; @@ -183,7 +185,7 @@ public: Decrypts a cyphertext string encrypted with this class with the set key back to the plain text version. - If an error occurred, such as non-matching keys between encryption and decryption, + 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) ; @@ -191,7 +193,7 @@ public: Decrypts a cyphertext binary encrypted with this class with the set key back to the plain text version. - If an error occurred, such as non-matching keys between encryption and decryption, + 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) ; @@ -199,7 +201,7 @@ public: Decrypts a cyphertext binary encrypted with this class with the set key back to the plain text version. - If an error occurred, such as non-matching keys between encryption and decryption, + 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) ; @@ -221,6 +223,9 @@ private: CompressionMode m_compressionMode; IntegrityProtectionMode m_protectionMode; Error m_lastError; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + QRandomGenerator m_rand; +#endif }; Q_DECLARE_OPERATORS_FOR_FLAGS(O0SimpleCrypt::CryptoFlags) diff --git a/external/o2/src/o1.cpp b/external/o2/src/o1.cpp index bdfded60789..3fe3c83bae6 100644 --- a/external/o2/src/o1.cpp +++ b/external/o2/src/o1.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #if QT_VERSION >= 0x050000 #include @@ -15,20 +16,32 @@ #include #endif +#if QT_VERSION >= 0x051500 +#include +#endif + + #include "o1.h" #include "o2replyserver.h" #include "o0globals.h" #include "o0settingsstore.h" -O1::O1(QObject *parent, QNetworkAccessManager *manager): O0BaseAuth(parent) { - setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); +O1::O1(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) { + setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA256); manager_ = manager ? manager : new QNetworkAccessManager(this); - replyServer_ = new O2ReplyServer(this); qRegisterMetaType("QNetworkReply::NetworkError"); - connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + setCallbackUrl(O2_CALLBACK_URL); } +QByteArray O1::userAgent() const { + return userAgent_; +} + +void O1::setUserAgent(const QByteArray &v) { + userAgent_ = v; +} + QUrl O1::requestTokenUrl() { return requestTokenUrl_; } @@ -141,7 +154,7 @@ QByteArray O1::getRequestBase(const QList &oauthParams, cons // Append a sorted+encoded list of all request parameters to the base string QList headers(oauthParams); headers.append(otherParams); - qSort(headers); + std::sort(headers.begin(), headers.end()); base.append(encodeHeaders(headers)); return base; @@ -151,7 +164,7 @@ QByteArray O1::sign(const QList &oauthParams, const QList= 0x050100 - return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha1).toBase64(); + return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha256).toBase64(); #else return hmacSha1(secret, baseString); #endif @@ -161,7 +174,7 @@ QByteArray O1::buildAuthorizationHeader(const QList &oauthPa bool first = true; QByteArray ret("OAuth "); QList headers(oauthParams); - qSort(headers); + std::sort(headers.begin(), headers.end()); foreach (O0RequestParameter h, headers) { if (first) { first = false; @@ -176,9 +189,20 @@ QByteArray O1::buildAuthorizationHeader(const QList &oauthPa return ret; } +void O1::decorateRequest(QNetworkRequest &req, const QList &oauthParams) { + req.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); + if (!userAgent_.isEmpty()) { +#if QT_VERSION >= 0x050000 + req.setHeader(QNetworkRequest::UserAgentHeader, userAgent_); +#else + req.setRawHeader("User-Agent", userAgent_); +#endif + } +} + QByteArray O1::generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) { QByteArray signature; - if (signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA1) { + if (signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA256) { signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret()); } else if (signatureMethod() == O2_SIGNATURE_TYPE_PLAINTEXT) { signature = clientSecret().toLatin1() + "&" + tokenSecret().toLatin1(); @@ -188,6 +212,17 @@ QByteArray O1::generateSignature(const QList headers, const void O1::link() { qDebug() << "O1::link"; + + // Create the reply server if it doesn't exist + // and we don't use an external web interceptor + if(!useExternalWebInterceptor_) { + if(replyServer() == NULL) { + O2ReplyServer * replyServer = new O2ReplyServer(this); + connect(replyServer, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + setReplyServer(replyServer); + } + } + if (linked()) { qDebug() << "O1::link: Linked already"; Q_EMIT linkingSucceeded(); @@ -199,9 +234,11 @@ void O1::link() { setTokenSecret(""); setExtraTokens(QVariantMap()); - // Start reply server - if (!replyServer_->isListening()) - replyServer_->listen(QHostAddress::Any, localPort()); + if (!useExternalWebInterceptor_) { + // Start reply server + if (!replyServer()->isListening()) + replyServer()->listen(QHostAddress::Any, localPort()); + } // Get any query parameters for the request #if QT_VERSION >= 0x050000 @@ -226,23 +263,35 @@ void O1::link() { // Create initial token request QList headers; - headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1())); + headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); +#if QT_VERSION >= 0x050800 + headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1())); +#else headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); +#endif headers.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, requestParameters(), QNetworkAccessManager::PostOperation))); + // qDebug() << "O1:link: Token request headers:"; + // foreach(param, headers) { + // qDebug() << " " << param.name << "=" << param.value; + // } // Clear request token requestToken_.clear(); requestTokenSecret_.clear(); // Post request - request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(headers)); + decorateRequest(request, headers); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, QByteArray()); +#if QT_VERSION < 0x051500 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError))); +#else + connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError))); +#endif connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished())); } @@ -282,11 +331,11 @@ void O1::onTokenRequestFinished() { QUrl url(authorizeUrl()); #if QT_VERSION < 0x050000 url.addQueryItem(O2_OAUTH_TOKEN, requestToken_); - url.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1()); + url.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1()); #else QUrlQuery query(url); query.addQueryItem(O2_OAUTH_TOKEN, requestToken_); - query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1()); + query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1()); url.setQuery(query); #endif Q_EMIT openBrowser(url); @@ -313,7 +362,11 @@ void O1::exchangeToken() { QList oauthParams; oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); +#if QT_VERSION >= 0x050800 + oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1())); +#else oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); +#endif oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1())); @@ -321,10 +374,14 @@ void O1::exchangeToken() { oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList(), QNetworkAccessManager::PostOperation))); // Post request - request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); + decorateRequest(request, oauthParams); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, QByteArray()); +#if QT_VERSION < 0x051500 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); +#else + connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); +#endif connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); } @@ -378,12 +435,20 @@ QMap O1::parseResponse(const QByteArray &response) { } QByteArray O1::nonce() { +#if QT_VERSION >= 0x050800 + QString u = QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1(); +#else + QString u = QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1(); +#endif +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + u.append(QString::number(QRandomGenerator::global()->generate())); +#else 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())); +#endif return u.toLatin1(); } diff --git a/external/o2/src/o1.h b/external/o2/src/o1.h index 6a0a28b0846..b54abd5a50c 100644 --- a/external/o2/src/o1.h +++ b/external/o2/src/o1.h @@ -8,13 +8,19 @@ #include "o0export.h" #include "o0baseauth.h" -class O2ReplyServer; - /// Simple OAuth 1.0 authenticator. class O0_EXPORT O1: public O0BaseAuth { Q_OBJECT public: + /// HTTP User-Agent header + /// Set user agent to a value unique for your application (https://tools.ietf.org/html/rfc7231#section-5.5.3) + /// if you see the following error in the application log: + /// O1::onTokenRequestError: 201 "Error transferring requestTokenUrl() - server replied: Forbidden" "Bad bot" + Q_PROPERTY(QByteArray userAgent READ userAgent WRITE setUserAgent) + QByteArray userAgent() const; + void setUserAgent(const QByteArray &value); + /// Signature method Q_PROPERTY(QString signatureMethod READ signatureMethod WRITE setSignatureMethod NOTIFY signatureMethodChanged) QString signatureMethod(); @@ -26,7 +32,7 @@ public: void setRequestTokenUrl(const QUrl &value); /// Parameters to pass with request URL. - Q_PROPERTY(QList requestParameters READ requestParameters WRITE setRequestParameters); + Q_PROPERTY(QList requestParameters READ requestParameters WRITE setRequestParameters) QList requestParameters(); void setRequestParameters(const QList &value); @@ -48,7 +54,7 @@ public: void setAccessTokenUrl(const QUrl &value); /// Constructor. - explicit O1(QObject *parent = 0, QNetworkAccessManager *manager = 0); + explicit O1(QObject *parent = 0, QNetworkAccessManager *manager = 0, O0AbstractStore *store = 0); /// Parse a URL-encoded response string. static QMap parseResponse(const QByteArray &response); @@ -56,6 +62,9 @@ public: /// Build the value of the "Authorization:" header. static QByteArray buildAuthorizationHeader(const QList &oauthParams); + /// Add common configuration (headers) to @p req. + void decorateRequest(QNetworkRequest &req, const QList &oauthParams); + /// Create unique bytes to prevent replay attacks. static QByteArray nonce(); @@ -91,10 +100,11 @@ Q_SIGNALS: void accessTokenUrlChanged(); void signatureMethodChanged(); -protected Q_SLOTS: +public Q_SLOTS: /// Handle verification received from the reply server. virtual void onVerificationReceived(QMap params); +protected Q_SLOTS: /// Handle token request error. virtual void onTokenRequestError(QNetworkReply::NetworkError error); @@ -111,6 +121,7 @@ protected: /// Exchange temporary token to authentication token void exchangeToken(); + QByteArray userAgent_; QUrl requestUrl_; QList requestParameters_; QString callbackUrl_; @@ -119,7 +130,6 @@ protected: QString verifier_; QString signatureMethod_; QNetworkAccessManager *manager_; - O2ReplyServer *replyServer_; }; #endif // O1_H diff --git a/external/o2/src/o1requestor.cpp b/external/o2/src/o1requestor.cpp index fabb4facc21..f006547f21d 100644 --- a/external/o2/src/o1requestor.cpp +++ b/external/o2/src/o1requestor.cpp @@ -45,13 +45,17 @@ QNetworkRequest O1Requestor::setup(const QNetworkRequest &req, const QListtoken().toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, authenticator_->signatureMethod().toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, O1::nonce())); +#if QT_VERSION >= 0x050800 + oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1())); +#else oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); +#endif // Add signature parameter oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, authenticator_->generateSignature(oauthParams, req, signingParameters, operation))); // Return a copy of the original request with authorization header set QNetworkRequest request(req); - request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, O1::buildAuthorizationHeader(oauthParams)); + authenticator_->decorateRequest(request, oauthParams); return request; } diff --git a/external/o2/src/o1smugmug.cpp b/external/o2/src/o1smugmug.cpp new file mode 100644 index 00000000000..3c6562b7e1c --- /dev/null +++ b/external/o2/src/o1smugmug.cpp @@ -0,0 +1,86 @@ +#include + +#include "o1smugmug.h" + +static QString basicAuthorizationUrl() { + return "https://secure.smugmug.com/services/oauth/1.0a/authorize"; +} + +static const char *accessToString(O1SmugMug::Access access) { + const char * const strings[] = { + "Public", + "Full" + }; + const std::size_t stringsSize = sizeof(strings) / sizeof(strings[0]); + Q_ASSERT(access >= 0 && static_cast(access) < stringsSize + && "Unsupported SmugMug authorization access!"); + Q_UNUSED(stringsSize) + + return strings[access]; +} + +static const char *permissionsToString(O1SmugMug::Permissions permissions) { + const char * const strings[] = { + "Read", + "Add", + "Modify" + }; + const std::size_t stringsSize = sizeof(strings) / sizeof(strings[0]); + Q_ASSERT(permissions >= 0 && static_cast(permissions) < stringsSize + && "Unsupported SmugMug authorization permissions!"); + Q_UNUSED(stringsSize) + + return strings[permissions]; +} + + +void O1SmugMug::initAuthorizationUrl(Access access, Permissions permissions) { + setAuthorizeUrl(QUrl(::basicAuthorizationUrl() + + "?Access=" + ::accessToString(access) + + "&Permissions=" + ::permissionsToString(permissions))); +} + +#if QT_VERSION >= 0x050000 + +void O1SmugMug::AuthorizationUrlBuilder::setAccess(Access value) { + query_.addQueryItem("Access", ::accessToString(value)); +} + +void O1SmugMug::AuthorizationUrlBuilder::setPermissions(Permissions value) { + query_.addQueryItem("Permissions", ::permissionsToString(value)); +} + +void O1SmugMug::AuthorizationUrlBuilder::setAllowThirdPartyLogin(bool value) { + query_.addQueryItem("allowThirdPartyLogin", value ? "1" : "0"); +} + +void O1SmugMug::AuthorizationUrlBuilder::setShowSignUpButton(bool value) { + query_.addQueryItem("showSignUpButton", value ? "true" : "false"); +} + +void O1SmugMug::AuthorizationUrlBuilder::setPrepopulatedUsername(const QString &value) { + query_.addQueryItem("username", value); +} + +void O1SmugMug::AuthorizationUrlBuilder::setViewportScale(double value) { + Q_ASSERT(value >= 0 && value <= 1 && "Invalid SmugMug authorization viewportScale!"); + query_.addQueryItem("viewportScale", QString::number(value, 'f')); +} + +QUrl O1SmugMug::AuthorizationUrlBuilder::url() const { + QUrl result(::basicAuthorizationUrl()); + result.setQuery(query_); + return result; +} + +void O1SmugMug::initAuthorizationUrl(const AuthorizationUrlBuilder &builder) { + setAuthorizeUrl(builder.url()); +} + +#endif // QT_VERSION >= 0x050000 + +O1SmugMug::O1SmugMug(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store) + : O1(parent, manager, store) { + setRequestTokenUrl(QUrl("https://secure.smugmug.com/services/oauth/1.0a/getRequestToken")); + setAccessTokenUrl(QUrl("https://secure.smugmug.com/services/oauth/1.0a/getAccessToken")); +} diff --git a/external/o2/src/o1smugmug.h b/external/o2/src/o1smugmug.h new file mode 100644 index 00000000000..902058ea64d --- /dev/null +++ b/external/o2/src/o1smugmug.h @@ -0,0 +1,69 @@ +#ifndef O1SMUGMUG_H +#define O1SMUGMUG_H + +#include "o0export.h" +#include "o1.h" + +#if QT_VERSION >= 0x050000 +#include +#endif + +/// SmugMug OAuth 1.0 client +/// +/// Simple usage: +/// @code +/// o1_ = new O1SmugMug(this); +/// o1_->initAuthorizationUrl(O1SmugMug::AccessFull, O1SmugMug::PermissionsAdd); +/// @endcode +/// +/// Advanced usage (Qt 5.0 or later): +/// @code +/// o1_ = new O1SmugMug(this, 0, secureStore); +/// O1SmugMug::AuthorizationUrlBuilder builder; +/// builder.setAccess(O1SmugMug::AccessFull); +/// builder.setShowSignUpButton(false); +/// builder.setPrepopulatedUsername(lastUsername_); +/// o1_->initAuthorizationUrl(builder); +/// @endcode +class O0_EXPORT O1SmugMug: public O1 { + Q_OBJECT + Q_ENUMS(Access) + Q_ENUMS(Permissions) + +public: + enum Access { + AccessPublic, + AccessFull + }; + + enum Permissions { + PermissionsRead, + PermissionsAdd, + PermissionsModify + }; + + Q_INVOKABLE void initAuthorizationUrl(Access access, Permissions permissions); + +#if QT_VERSION >= 0x050000 + class AuthorizationUrlBuilder { + public: + void setAccess(Access value); + void setPermissions(Permissions value); + void setAllowThirdPartyLogin(bool value); + void setShowSignUpButton(bool value); + void setPrepopulatedUsername(const QString &value); + void setViewportScale(double value); + + QUrl url() const; + + private: + QUrlQuery query_; + }; + + void initAuthorizationUrl(const AuthorizationUrlBuilder &builder); +#endif // QT_VERSION >= 0x050000 + + explicit O1SmugMug(QObject *parent = 0, QNetworkAccessManager *manager = 0, O0AbstractStore *store = 0); +}; + +#endif // O1SMUGMUG_H diff --git a/external/o2/src/o1upwork.h b/external/o2/src/o1upwork.h new file mode 100644 index 00000000000..2fcd6d5c423 --- /dev/null +++ b/external/o2/src/o1upwork.h @@ -0,0 +1,18 @@ +#ifndef O1UPWORK_H +#define O1UPWORK_H + +#include "o1.h" + +class O1Upwork: public O1 { + Q_OBJECT + +public: + explicit O1Upwork(QObject *parent = 0) : O1(parent) { + setRequestTokenUrl(QUrl("https://www.upwork.com/api/auth/v1/oauth/token/request")); + setAuthorizeUrl(QUrl("https://www.upwork.com/services/api/auth")); + setAccessTokenUrl(QUrl("https://www.upwork.com/api/auth/v1/oauth/token/access")); + } +}; + +#endif // O1UPWORK_H + diff --git a/external/o2/src/o2.cpp b/external/o2/src/o2.cpp index 5cc8ce3ff65..39dd648950e 100644 --- a/external/o2/src/o2.cpp +++ b/external/o2/src/o2.cpp @@ -10,52 +10,25 @@ #include #include #include +#include #if QT_VERSION >= 0x050000 #include -#include -#include +#endif + +#if QT_VERSION >= 0x050000 +#include #else -#include -#include +#include #endif #include "o2.h" +#include "o2pollserver.h" #include "o2replyserver.h" #include "o0globals.h" +#include "o0jsonresponse.h" #include "o0settingsstore.h" -/// Parse JSON data into a QVariantMap -static QVariantMap parseTokenResponse(const QByteArray &data) { -#if QT_VERSION >= 0x050000 - QJsonParseError err; - QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) { - qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString(); - return QVariantMap(); - } - - if (!doc.isObject()) { - qWarning() << "parseTokenResponse: Token response is not an object"; - return QVariantMap(); - } - - return doc.object().toVariantMap(); -#else - QScriptEngine engine; - QScriptValue value = engine.evaluate("(" + QString(data) + ")"); - QScriptValueIterator it(value); - QVariantMap map; - - while (it.hasNext()) { - it.next(); - map.insert(it.name(), it.value().toVariant()); - } - - return map; -#endif -} - /// Add query parameters to a query static void addQueryParametersToUrl(QUrl &url, QList > parameters) { #if QT_VERSION < 0x050000 @@ -67,14 +40,30 @@ static void addQueryParametersToUrl(QUrl &url, QList > #endif } -O2::O2(QObject *parent, QNetworkAccessManager *manager): O0BaseAuth(parent) { +// ref: https://tools.ietf.org/html/rfc8628#section-3.2 +// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both. +static bool hasMandatoryDeviceAuthParams(const QVariantMap& params) +{ + if (!params.contains(O2_OAUTH2_DEVICE_CODE)) + return false; + + if (!params.contains(O2_OAUTH2_USER_CODE)) + return false; + + if (!(params.contains(O2_OAUTH2_VERIFICATION_URI) || params.contains(O2_OAUTH2_VERIFICATION_URL))) + return false; + + if (!params.contains(O2_OAUTH2_EXPIRES_IN)) + return false; + + return true; +} + +O2::O2(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) { manager_ = manager ? manager : new QNetworkAccessManager(this); - replyServer_ = new O2ReplyServer(this); grantFlow_ = GrantFlowAuthorizationCode; localhostPolicy_ = QString(O2_CALLBACK_URL); qRegisterMetaType("QNetworkReply::NetworkError"); - connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); - connect(replyServer_, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool))); } O2::GrantFlow O2::grantFlow() { @@ -151,9 +140,44 @@ void O2::setRefreshTokenUrl(const QString &value) { Q_EMIT refreshTokenUrlChanged(); } +QString O2::grantType() +{ + if (!grantType_.isEmpty()) + return grantType_; + + switch (grantFlow_) { + case GrantFlowAuthorizationCode: + return O2_OAUTH2_GRANT_TYPE_CODE; + case GrantFlowImplicit: + return O2_OAUTH2_GRANT_TYPE_TOKEN; + case GrantFlowResourceOwnerPasswordCredentials: + return O2_OAUTH2_GRANT_TYPE_PASSWORD; + case GrantFlowDevice: + return O2_OAUTH2_GRANT_TYPE_DEVICE; + } + + return QString(); +} + +void O2::setGrantType(const QString &value) +{ + grantType_ = value; +} + void O2::link() { //qDebug() << "O2::link"; + // Create the reply server if it doesn't exist + // and we don't use an external web interceptor + if(!useExternalWebInterceptor_) { + if(replyServer() == NULL) { + O2ReplyServer * replyServer = new O2ReplyServer(this); + connect(replyServer, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + connect(replyServer, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool))); + setReplyServer(replyServer); + } + } + if (linked()) { //qDebug() << "O2::link: Linked already"; Q_EMIT linkingSucceeded(); @@ -168,19 +192,31 @@ void O2::link() { setExpires(0); if (grantFlow_ == GrantFlowAuthorizationCode || grantFlow_ == GrantFlowImplicit) { - // Start listening to authentication replies - if (!replyServer_->isListening()) { - if (replyServer_->listen(QHostAddress::Any, localPort_)) { - //qDebug() << "O2::link: Reply server listening on port" << localPort(); - } else { - qWarning() << "O2::link: Reply server failed to start listening on port" << localPort(); - Q_EMIT linkingFailed(); - return; - } - } - // Save redirect URI, as we have to reuse it when requesting the access token - redirectUri_ = localhostPolicy_.arg(replyServer_->serverPort()); +#if QT_VERSION >= 0x050000 + QString uniqueState = QUuid::createUuid().toString().remove(QRegularExpression("([^a-zA-Z0-9]|[-])")); +#else + QString uniqueState = QUuid::createUuid().toString().remove(QRegExp("([^a-zA-Z0-9]|[-])")); +#endif + if (useExternalWebInterceptor_) { + // Save redirect URI, as we have to reuse it when requesting the access token + redirectUri_ = localhostPolicy_.arg(localPort()); + } else { + // Start listening to authentication replies + if (!replyServer()->isListening()) { + if (replyServer()->listen(QHostAddress::Any, localPort_)) { + //qDebug() << "O2::link: Reply server listening on port" << localPort(); + } else { + qWarning() << "O2::link: Reply server failed to start listening on port" << localPort(); + Q_EMIT linkingFailed(); + return; + } + } + + // Save redirect URI, as we have to reuse it when requesting the access token + redirectUri_ = localhostPolicy_.arg(replyServer()->serverPort()); + replyServer()->setUniqueState(uniqueState); + } // Assemble intial authentication URL QList > parameters; @@ -189,6 +225,7 @@ void O2::link() { 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_.replace( " ", "+" ))); + parameters.append(qMakePair(QString(O2_OAUTH2_STATE), uniqueState)); if ( !apiKey_.isEmpty() ) parameters.append(qMakePair(QString(O2_OAUTH2_API_KEY), apiKey_)); foreach (QString key, extraRequestParams().keys()) { @@ -222,12 +259,34 @@ void O2::link() { QNetworkReply *tokenReply = getManager()->post(tokenRequest, payload); connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + } + else if (grantFlow_ == GrantFlowDevice) { + QList parameters; + parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_SCOPE, scope_.toUtf8())); + QByteArray payload = O0BaseAuth::createQueryParameters(parameters); + + QUrl url(requestUrl_); + QNetworkRequest deviceRequest(url); + deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QNetworkReply *tokenReply = getManager()->post(deviceRequest, payload); + + connect(tokenReply, SIGNAL(finished()), this, SLOT(onDeviceAuthReplyFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 + connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif } } void O2::unlink() { - //() << "O2::unlink"; + //qDebug() << "O2::unlink"; setLinked(false); setToken(QString()); setRefreshToken(QString()); @@ -237,7 +296,6 @@ void O2::unlink() { } void O2::onVerificationReceived(const QMap response) { - //qDebug() << "O2::onVerificationReceived:" << response; //qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()"; Q_EMIT closeBrowser(); @@ -271,24 +329,31 @@ void O2::onVerificationReceived(const QMap response) { QNetworkReply *tokenReply = getManager()->post(tokenRequest, data); timedReplies_.add(tokenReply); connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); - } else if (grantFlow_ == GrantFlowImplicit) { +#else + connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + } else if (grantFlow_ == GrantFlowImplicit || grantFlow_ == GrantFlowDevice) { // Check for mandatory tokens if (response.contains(O2_OAUTH2_ACCESS_TOKEN)) { - //qDebug() << "O2::onVerificationReceived: Access token returned for implicit flow"; + //qDebug() << "O2::onVerificationReceived: Access token returned for implicit or device flow"; setToken(response.value(O2_OAUTH2_ACCESS_TOKEN)); if (response.contains(O2_OAUTH2_EXPIRES_IN)) { bool ok = false; int expiresIn = response.value(O2_OAUTH2_EXPIRES_IN).toInt(&ok); if (ok) { //qDebug() << "O2::onVerificationReceived: Token expires in" << expiresIn << "seconds"; - setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn); + setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn)); } } + if (response.contains(O2_OAUTH2_REFRESH_TOKEN)) { + setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN)); + } setLinked(true); Q_EMIT linkingSucceeded(); } else { - qWarning() << "O2::onVerificationReceived: Access token missing from response for implicit flow"; + qWarning() << "O2::onVerificationReceived: Access token missing from response for implicit or device flow"; Q_EMIT linkingFailed(); } } else { @@ -323,7 +388,7 @@ void O2::onTokenReplyFinished() { //qDebug() << "O2::onTokenReplyFinished: replyData\n"; //qDebug() << QString( replyData ); - QVariantMap tokens = parseTokenResponse(replyData); + QVariantMap tokens = parseJsonResponse(replyData); // Dump tokens //qDebug() << "O2::onTokenReplyFinished: Tokens returned:\n"; @@ -340,7 +405,7 @@ void O2::onTokenReplyFinished() { int expiresIn = tokens.take(O2_OAUTH2_EXPIRES_IN).toInt(&ok); if (ok) { // qDebug() << "O2::onTokenReplyFinished: Token expires in" << expiresIn << "seconds"; - setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn); + setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn)); } setRefreshToken(tokens.take(O2_OAUTH2_REFRESH_TOKEN).toString()); setExtraTokens(tokens); @@ -357,11 +422,17 @@ void O2::onTokenReplyFinished() { void O2::onTokenReplyError(QNetworkReply::NetworkError error) { QNetworkReply *tokenReply = qobject_cast(sender()); - qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString(); - // qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll(); + if (!tokenReply) + { + //qDebug() << "O2::onTokenReplyError: reply is null"; + } else { + qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString(); + //qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll(); + timedReplies_.remove(tokenReply); + } + setToken(QString()); setRefreshToken(QString()); - timedReplies_.remove(tokenReply); Q_EMIT linkingFailed(); } @@ -395,6 +466,45 @@ QNetworkAccessManager *O2::getManager() return manager_; } +void O2::startPollServer(const QVariantMap ¶ms) +{ + bool ok = false; + int expiresIn = params[O2_OAUTH2_EXPIRES_IN].toInt(&ok); + if (!ok) { + qWarning() << "O2::startPollServer: No expired_in parameter"; + Q_EMIT linkingFailed(); + return; + } + + //qDebug() << "O2::startPollServer: device_ and user_code expires in" << expiresIn << "seconds"; + + QUrl url(tokenUrl_); + QNetworkRequest authRequest(url); + authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + const QString deviceCode = params[O2_OAUTH2_DEVICE_CODE].toString(); + const QString grantType = grantType_.isEmpty() ? O2_OAUTH2_GRANT_TYPE_DEVICE : grantType_; + + QList parameters; + parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8())); + if ( !clientSecret_.isEmpty() ) + parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_SECRET, clientSecret_.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_CODE, deviceCode.toUtf8())); + parameters.append(O0RequestParameter(O2_OAUTH2_GRANT_TYPE, grantType.toUtf8())); + QByteArray payload = O0BaseAuth::createQueryParameters(parameters); + + O2PollServer * pollServer = new O2PollServer(getManager(), authRequest, payload, expiresIn, this); + if (params.contains(O2_OAUTH2_INTERVAL)) { + int interval = params[O2_OAUTH2_INTERVAL].toInt(&ok); + if (ok) + pollServer->setInterval(interval); + } + connect(pollServer, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); + connect(pollServer, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool))); + setPollServer(pollServer); + pollServer->startPolling(); +} + QString O2::refreshToken() { QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_); return store_->value(key); @@ -432,35 +542,44 @@ void O2::refresh() { QNetworkReply *refreshReply = getManager()->post(refreshRequest, data); timedReplies_.add(refreshReply); connect(refreshReply, SIGNAL(finished()), this, SLOT(onRefreshFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(refreshReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif } void O2::onRefreshFinished() { QNetworkReply *refreshReply = qobject_cast(sender()); + if (refreshReply->error() == QNetworkReply::NoError) { QByteArray reply = refreshReply->readAll(); - QVariantMap tokens = parseTokenResponse(reply); + QVariantMap tokens = parseJsonResponse(reply); if ( tokens.contains(QStringLiteral("error")) ) { qDebug() << " Error refreshing token" << tokens.value(QStringLiteral("error")).toMap().value(QStringLiteral("message")).toString().toLocal8Bit().constData(); unlink(); + timedReplies_.remove(refreshReply); + Q_EMIT refreshFinished(QNetworkReply::NoError); } else { setToken(tokens.value(O2_OAUTH2_ACCESS_TOKEN).toString()); - setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt()); - const QString refreshToken = tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString(); - if ( !refreshToken.isEmpty() ) - setRefreshToken(refreshToken); + setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt())); + QString refreshToken = tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString(); + if(!refreshToken.isEmpty()) { + setRefreshToken(refreshToken); + } + else { + //qDebug() << "No new refresh token. Keep the old one."; + } + timedReplies_.remove(refreshReply); setLinked(true); - //qDebug() << " New token expires in" << expires() << "seconds"; Q_EMIT linkingSucceeded(); + Q_EMIT refreshFinished(QNetworkReply::NoError); + //qDebug() << " New token expires in" << expires() << "seconds"; } - timedReplies_.remove(refreshReply); - Q_EMIT refreshFinished(QNetworkReply::NoError); - } - else - { - qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); + } else { + //qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); } refreshReply->deleteLater(); } @@ -473,12 +592,63 @@ void O2::onRefreshError(QNetworkReply::NetworkError error) { Q_EMIT refreshFinished(error); } +void O2::onDeviceAuthReplyFinished() +{ + qDebug() << "O2::onDeviceAuthReplyFinished"; + QNetworkReply *tokenReply = qobject_cast(sender()); + if (!tokenReply) + { + qDebug() << "O2::onDeviceAuthReplyFinished: reply is null"; + return; + } + if (tokenReply->error() == QNetworkReply::NoError) { + QByteArray replyData = tokenReply->readAll(); + + // Dump replyData + // SENSITIVE DATA in RelWithDebInfo or Debug builds + //qDebug() << "O2::onDeviceAuthReplyFinished: replyData\n"; + //qDebug() << QString( replyData ); + + QVariantMap params = parseJsonResponse(replyData); + + // Dump tokens + qDebug() << "O2::onDeviceAuthReplyFinished: Tokens returned:\n"; + foreach (QString key, params.keys()) { + // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first + qDebug() << key << ": "<< params.value( key ).toString().left( 3 ) << "..."; + } + + // Check for mandatory parameters + if (hasMandatoryDeviceAuthParams(params)) { + qDebug() << "O2::onDeviceAuthReplyFinished: Device auth request response"; + + const QString userCode = params.take(O2_OAUTH2_USER_CODE).toString(); + QUrl uri = params.take(O2_OAUTH2_VERIFICATION_URI).toUrl(); + if (uri.isEmpty()) + uri = params.take(O2_OAUTH2_VERIFICATION_URL).toUrl(); + + if (params.contains(O2_OAUTH2_VERIFICATION_URI_COMPLETE)) + Q_EMIT openBrowser(params.take(O2_OAUTH2_VERIFICATION_URI_COMPLETE).toUrl()); + + Q_EMIT showVerificationUriAndCode(uri, userCode); + + startPollServer(params); + } else { + qWarning() << "O2::onDeviceAuthReplyFinished: Mandatory parameters missing from response"; + Q_EMIT linkingFailed(); + } + } + tokenReply->deleteLater(); +} + void O2::serverHasClosed(bool paramsfound) { if ( !paramsfound ) { // server has probably timed out after receiving first response Q_EMIT linkingFailed(); } + // poll server is not re-used for later auth requests + setPollServer(NULL); } QString O2::localhostPolicy() const { @@ -497,14 +667,6 @@ void O2::setApiKey(const QString &value) { apiKey_ = value; } -QByteArray O2::replyContent() { - return replyServer_->replyContent(); -} - -void O2::setReplyContent(const QByteArray &value) { - replyServer_->setReplyContent(value); -} - bool O2::ignoreSslErrors() { return timedReplies_.ignoreSslErrors(); } diff --git a/external/o2/src/o2.h b/external/o2/src/o2.h index af8c87b7c5d..0756686e27b 100644 --- a/external/o2/src/o2.h +++ b/external/o2/src/o2.h @@ -11,11 +11,11 @@ #include "o2reply.h" #include "o0abstractstore.h" -class O2ReplyServer; - /// Simple OAuth2 authenticator. -class O0_EXPORT O2: public O0BaseAuth { +class O0_EXPORT O2: public O0BaseAuth +{ Q_OBJECT +public: Q_ENUMS(GrantFlow) public: @@ -25,6 +25,7 @@ public: GrantFlowImplicit, ///< @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.2 GrantFlowResourceOwnerPasswordCredentials, GrantFlowPkce, ///< @see https://www.rfc-editor.org/rfc/rfc7636 + GrantFlowDevice ///< @see https://tools.ietf.org/html/rfc8628#section-1 }; /// Authorization flow. @@ -60,12 +61,6 @@ public: QString apiKey(); void setApiKey(const QString &value); - /// Page content on local host after successful oauth. - /// Provide it in case you do not want to close the browser, but display something - Q_PROPERTY(QByteArray replyContent READ replyContent WRITE setReplyContent) - QByteArray replyContent(); - void setReplyContent(const QByteArray &value); - /// Allow ignoring SSL errors? /// E.g. SurveyMonkey fails on Mac due to SSL error. Ignoring the error circumvents the problem Q_PROPERTY(bool ignoreSslErrors READ ignoreSslErrors WRITE setIgnoreSslErrors) @@ -92,10 +87,15 @@ public: QString refreshTokenUrl(); void setRefreshTokenUrl(const QString &value); + /// Grant type (if non-standard) + Q_PROPERTY(QString grantType READ grantType WRITE setGrantType) + QString grantType(); + void setGrantType(const QString &value); + public: /// Constructor. /// @param parent Parent object. - explicit O2(QObject *parent = 0, QNetworkAccessManager *manager = 0); + explicit O2(QObject *parent = 0, QNetworkAccessManager *manager = 0, O0AbstractStore *store = 0); /// Get authentication code. QString code(); @@ -133,10 +133,11 @@ Q_SIGNALS: void refreshTokenUrlChanged(); void tokenUrlChanged(); -protected Q_SLOTS: +public Q_SLOTS: /// Handle verification response. virtual void onVerificationReceived(QMap); +protected Q_SLOTS: /// Handle completion of a token request. virtual void onTokenReplyFinished(); @@ -149,6 +150,9 @@ protected Q_SLOTS: /// Handle failure of a refresh request. virtual void onRefreshError(QNetworkReply::NetworkError error); + /// Handle completion of a Device Authorization Request + virtual void onDeviceAuthReplyFinished(); + protected: /// Build HTTP request body. QByteArray buildRequestBody(const QMap ¶meters); @@ -163,6 +167,8 @@ protected: void setExpires(int v); virtual QNetworkAccessManager *getManager(); + /// Start polling authorization server + void startPollServer(const QVariantMap ¶ms); protected: QString username_; @@ -176,9 +182,9 @@ protected: QString localhostPolicy_; QString apiKey_; QNetworkAccessManager *manager_; - O2ReplyServer *replyServer_; O2ReplyList timedReplies_; GrantFlow grantFlow_; + QString grantType_; }; #endif // O2_H diff --git a/external/o2/src/o2facebook.cpp b/external/o2/src/o2facebook.cpp index e6011346c62..20778a7e6c0 100644 --- a/external/o2/src/o2facebook.cpp +++ b/external/o2/src/o2facebook.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -13,7 +15,7 @@ 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 char *FbExpiresKey = "expires"; +static const char *FbExpiresKey = "expires_in"; O2Facebook::O2Facebook(QObject *parent): O2(parent) { setRequestUrl(FbEndpoint); @@ -58,7 +60,11 @@ void O2Facebook::onVerificationReceived(const QMap response) { QNetworkReply *tokenReply = manager_->get(tokenRequest); timedReplies_.add(tokenReply); connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif } void O2Facebook::onTokenReplyFinished() { @@ -68,12 +74,12 @@ void O2Facebook::onTokenReplyFinished() { if (tokenReply->error() == QNetworkReply::NoError) { // Process reply QByteArray replyData = tokenReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(replyData); + const QJsonObject rootObject = doc.object(); + QVariantMap reply; - foreach (QString pair, QString(replyData).split("&")) { - QStringList kv = pair.split("="); - if (kv.length() == 2) { - reply.insert(kv[0], kv[1]); - } + for (const QString &key : rootObject.keys()) { + reply.insert(key, rootObject[key].toVariant()); } // Interpret reply diff --git a/external/o2/src/o2facebook.h b/external/o2/src/o2facebook.h index 85808407cb9..6889afea7a0 100644 --- a/external/o2/src/o2facebook.h +++ b/external/o2/src/o2facebook.h @@ -11,8 +11,10 @@ class O0_EXPORT O2Facebook: public O2 { public: explicit O2Facebook(QObject *parent = 0); -protected Q_SLOTS: +public Q_SLOTS: void onVerificationReceived(QMap); + +protected Q_SLOTS: virtual void onTokenReplyFinished(); }; diff --git a/external/o2/src/o2gft.cpp b/external/o2/src/o2gft.cpp index eff81cffc5d..c40e70e8e98 100644 --- a/external/o2/src/o2gft.cpp +++ b/external/o2/src/o2gft.cpp @@ -1,9 +1,7 @@ #include "o2gft.h" +#include "o2google.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): O2Google(parent) { setScope(GftScope); diff --git a/external/o2/src/o2google.h b/external/o2/src/o2google.h index ef94c2f573b..21e07e6aafd 100644 --- a/external/o2/src/o2google.h +++ b/external/o2/src/o2google.h @@ -5,6 +5,7 @@ #ifndef O2_O2GOOGLE_H #define O2_O2GOOGLE_H +#include "o2.h" class O2Google : public O2 { Q_OBJECT diff --git a/external/o2/src/o2googledevice.cpp b/external/o2/src/o2googledevice.cpp new file mode 100644 index 00000000000..565a857bd7e --- /dev/null +++ b/external/o2/src/o2googledevice.cpp @@ -0,0 +1,15 @@ +#include "o2googledevice.h" + +static const char *GoogleDeviceTokenUrl = "https://oauth2.googleapis.com/token"; +static const char *GoogleDeviceRefreshUrl = "https://oauth2.googleapis.com/token"; +static const char *GoogleDeviceEndpoint = "https://oauth2.googleapis.com/device/code"; +// Google uses a different grant type value than specified in RFC 8628 +static const char *GoogleDeviceGrantType = "http://oauth.net/grant_type/device/1.0"; + +O2GoogleDevice::O2GoogleDevice(QObject *parent) : O2(parent) { + setGrantFlow(GrantFlowDevice); + setGrantType(GoogleDeviceGrantType); + setRequestUrl(GoogleDeviceEndpoint); + setTokenUrl(GoogleDeviceTokenUrl); + setRefreshTokenUrl(GoogleDeviceRefreshUrl); +} diff --git a/external/o2/src/o2googledevice.h b/external/o2/src/o2googledevice.h new file mode 100644 index 00000000000..a2a84a8bfb3 --- /dev/null +++ b/external/o2/src/o2googledevice.h @@ -0,0 +1,16 @@ +#ifndef O2GOOGLEDEVICE_H +#define O2GOOGLEDEVICE_H + +#include "o0export.h" +#include "o2.h" + +/// "Google Sign-In for TVs and Devices", +/// A dialect of RFC 8628: OAuth 2.0 Device Authorization Grant +class O0_EXPORT O2GoogleDevice : public O2 { + Q_OBJECT + +public: + explicit O2GoogleDevice(QObject *parent = 0); +}; + +#endif // O2GOOGLEDEVICE_H diff --git a/external/o2/src/o2hubic.cpp b/external/o2/src/o2hubic.cpp index 5671cb9af16..e8213bf1232 100644 --- a/external/o2/src/o2hubic.cpp +++ b/external/o2/src/o2hubic.cpp @@ -1,5 +1,4 @@ #include "o2hubic.h" -#include "o2globals.h" #include "o2replyserver.h" #include diff --git a/external/o2/src/o2msgraph.cpp b/external/o2/src/o2msgraph.cpp new file mode 100644 index 00000000000..2274a75a9fe --- /dev/null +++ b/external/o2/src/o2msgraph.cpp @@ -0,0 +1,10 @@ +#include "o2msgraph.h" + +static const char *MsgraphEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; +static const char *MsgraphTokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + +O2Msgraph::O2Msgraph(QObject *parent): O2(parent) { + setRequestUrl(MsgraphEndpoint); + setTokenUrl(MsgraphTokenUrl); + setRefreshTokenUrl(MsgraphTokenUrl); +} diff --git a/external/o2/src/o2msgraph.h b/external/o2/src/o2msgraph.h new file mode 100644 index 00000000000..48db6c9f6bc --- /dev/null +++ b/external/o2/src/o2msgraph.h @@ -0,0 +1,15 @@ +#ifndef O2MSGRAPH_H +#define O2MSGRAPH_H + +#include "o0export.h" +#include "o2.h" + +/// Microsoft Graph's dialect of OAuth 2.0 +class O0_EXPORT O2Msgraph: public O2 { + Q_OBJECT + +public: + explicit O2Msgraph(QObject *parent = 0); +}; + +#endif // O2MSGRAPH_H diff --git a/external/o2/src/o2outlook.cpp b/external/o2/src/o2outlook.cpp new file mode 100644 index 00000000000..a70f1788a2a --- /dev/null +++ b/external/o2/src/o2outlook.cpp @@ -0,0 +1,14 @@ +#include "o2outlook.h" +#include "o0globals.h" + +O2Outlook::O2Outlook(QObject *parent): O2Skydrive(parent) { + setRequestUrl("https://login.microsoftonline.com/common/oauth2/v2.0/authorize"); + setTokenUrl("https://login.microsoftonline.com/common/oauth2/v2.0/token"); + setRefreshTokenUrl("https://login.microsoftonline.com/common/oauth2/v2.0/token"); + redirectUri_ = QString("https://login.live.com/oauth20_desktop.srf"); +} + +QUrl O2Outlook::redirectUrl() +{ + return redirectUri_; +} diff --git a/external/o2/src/o2outlook.h b/external/o2/src/o2outlook.h new file mode 100644 index 00000000000..5e4336a6ca5 --- /dev/null +++ b/external/o2/src/o2outlook.h @@ -0,0 +1,17 @@ +#ifndef O2OUTLOOK_H +#define O2OUTLOOK_H + +#include "o2skydrive.h" + +/// Outlook's dialect of OAuth 2.0 +class O0_EXPORT O2Outlook: public O2Skydrive +{ + Q_OBJECT +public: + explicit O2Outlook(QObject *parent = nullptr); + +public: + QUrl redirectUrl(); +}; + +#endif // O2OUTLOOK_H diff --git a/external/o2/src/o2pollserver.cpp b/external/o2/src/o2pollserver.cpp new file mode 100644 index 00000000000..0826ac994d2 --- /dev/null +++ b/external/o2/src/o2pollserver.cpp @@ -0,0 +1,117 @@ +#include +#include + +#include "o2pollserver.h" +#include "o0jsonresponse.h" + +static QMap toVerificationParams(const QVariantMap &map) +{ + QMap params; + for (QVariantMap::const_iterator i = map.constBegin(); + i != map.constEnd(); ++i) + { + params[i.key()] = i.value().toString(); + } + return params; +} + +O2PollServer::O2PollServer(QNetworkAccessManager *manager, const QNetworkRequest &request, const QByteArray &payload, int expiresIn, QObject *parent) + : QObject(parent) + , manager_(manager) + , request_(request) + , payload_(payload) + , expiresIn_(expiresIn) +{ + expirationTimer.setTimerType(Qt::VeryCoarseTimer); + expirationTimer.setInterval(expiresIn * 1000); + expirationTimer.setSingleShot(true); + connect(&expirationTimer, SIGNAL(timeout()), this, SLOT(onExpiration())); + expirationTimer.start(); + + pollTimer.setTimerType(Qt::VeryCoarseTimer); + pollTimer.setInterval(5 * 1000); + pollTimer.setSingleShot(true); + connect(&pollTimer, SIGNAL(timeout()), this, SLOT(onPollTimeout())); +} + +int O2PollServer::interval() const +{ + return pollTimer.interval() / 1000; +} + +void O2PollServer::setInterval(int interval) +{ + pollTimer.setInterval(interval * 1000); +} + +void O2PollServer::startPolling() +{ + if (expirationTimer.isActive()) { + pollTimer.start(); + } +} + +void O2PollServer::onPollTimeout() +{ + qDebug() << "O2PollServer::onPollTimeout: retrying"; + QNetworkReply * reply = manager_->post(request_, payload_); + connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); +} + +void O2PollServer::onExpiration() +{ + pollTimer.stop(); + Q_EMIT serverClosed(false); +} + +void O2PollServer::onReplyFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + + if (!reply) { + qDebug() << "O2PollServer::onReplyFinished: reply is null"; + return; + } + + QByteArray replyData = reply->readAll(); + QMap params = toVerificationParams(parseJsonResponse(replyData)); + + // Dump replyData + // SENSITIVE DATA in RelWithDebInfo or Debug builds + // qDebug() << "O2PollServer::onReplyFinished: replyData\n"; + // qDebug() << QString( replyData ); + + if (reply->error() == QNetworkReply::TimeoutError) { + // rfc8628#section-3.2 + // "On encountering a connection timeout, clients MUST unilaterally + // reduce their polling frequency before retrying. The use of an + // exponential backoff algorithm to achieve this, such as doubling the + // polling interval on each such connection timeout, is RECOMMENDED." + setInterval(interval() * 2); + pollTimer.start(); + } + else { + QString error = params.value("error"); + if (error == "slow_down") { + // rfc8628#section-3.2 + // "A variant of 'authorization_pending', the authorization request is + // still pending and polling should continue, but the interval MUST + // be increased by 5 seconds for this and all subsequent requests." + setInterval(interval() + 5); + pollTimer.start(); + } + else if (error == "authorization_pending") { + // keep trying - rfc8628#section-3.2 + // "The authorization request is still pending as the end user hasn't + // yet completed the user-interaction steps (Section 3.3)." + pollTimer.start(); + } + else { + expirationTimer.stop(); + Q_EMIT serverClosed(true); + // let O2 handle the other cases + Q_EMIT verificationReceived(params); + } + } + reply->deleteLater(); +} diff --git a/external/o2/src/o2pollserver.h b/external/o2/src/o2pollserver.h new file mode 100644 index 00000000000..69a2f9f6483 --- /dev/null +++ b/external/o2/src/o2pollserver.h @@ -0,0 +1,49 @@ +#ifndef O2POLLSERVER_H +#define O2POLLSERVER_H + +#include +#include +#include +#include +#include +#include + +#include "o0export.h" + +class QNetworkAccessManager; + +/// Poll an authorization server for token +class O0_EXPORT O2PollServer : public QObject +{ + Q_OBJECT + +public: + explicit O2PollServer(QNetworkAccessManager * manager, const QNetworkRequest &request, const QByteArray & payload, int expiresIn, QObject *parent = 0); + + /// Seconds to wait between polling requests + Q_PROPERTY(int interval READ interval WRITE setInterval) + int interval() const; + void setInterval(int interval); + +Q_SIGNALS: + void verificationReceived(QMap); + void serverClosed(bool); // whether it has found parameters + +public Q_SLOTS: + void startPolling(); + +protected Q_SLOTS: + void onPollTimeout(); + void onExpiration(); + void onReplyFinished(); + +protected: + QNetworkAccessManager *manager_; + const QNetworkRequest request_; + const QByteArray payload_; + const int expiresIn_; + QTimer expirationTimer; + QTimer pollTimer; +}; + +#endif // O2POLLSERVER_H diff --git a/external/o2/src/o2reply.cpp b/external/o2/src/o2reply.cpp index 4e031726256..fad8531c4cb 100644 --- a/external/o2/src/o2reply.cpp +++ b/external/o2/src/o2reply.cpp @@ -5,7 +5,11 @@ O2Reply::O2Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) { setSingleShot(true); +#if QT_VERSION < 0x051500 connect(this, SIGNAL(error(QNetworkReply::NetworkError)), reply, SIGNAL(error(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(this, SIGNAL(error(QNetworkReply::NetworkError)), reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif connect(this, SIGNAL(timeout()), this, SLOT(onTimeOut()), Qt::QueuedConnection); start(timeOut); } diff --git a/external/o2/src/o2reply.h b/external/o2/src/o2reply.h index 644fc6689a1..4f722b11e5a 100644 --- a/external/o2/src/o2reply.h +++ b/external/o2/src/o2reply.h @@ -29,7 +29,7 @@ public: }; /// List of O2Replies. -class O2ReplyList { +class O0_EXPORT O2ReplyList { public: O2ReplyList() { ignoreSslErrors_ = false; } diff --git a/external/o2/src/o2replyserver.cpp b/external/o2/src/o2replyserver.cpp index a4073f44ffb..9ca7963b550 100755 --- a/external/o2/src/o2replyserver.cpp +++ b/external/o2/src/o2replyserver.cpp @@ -12,6 +12,7 @@ #include #endif +#include "o0globals.h" #include "o2replyserver.h" O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent), @@ -73,6 +74,11 @@ void O2ReplyServer::onBytesReady() { return; } } + if (!uniqueState_.isEmpty() && !queryParams.contains(QString(O2_OAUTH2_STATE))) { + //qDebug() << "O2ReplyServer::onBytesReady: Malicious or service request"; + closeServer(socket, true); + return; // Malicious or service (e.g. favicon.ico) request + } //qDebug() << "O2ReplyServer::onBytesReady: Query params found, closing server"; closeServer(socket, true); Q_EMIT verificationReceived(queryParams); @@ -166,3 +172,13 @@ void O2ReplyServer::setCallbackTries(int maxtries) { maxtries_ = maxtries; } + +QString O2ReplyServer::uniqueState() +{ + return uniqueState_; +} + +void O2ReplyServer::setUniqueState(const QString &state) +{ + uniqueState_ = state; +} diff --git a/external/o2/src/o2replyserver.h b/external/o2/src/o2replyserver.h index 53c89237972..666b3309d7d 100644 --- a/external/o2/src/o2replyserver.h +++ b/external/o2/src/o2replyserver.h @@ -30,6 +30,9 @@ public: int callbackTries(); void setCallbackTries(int maxtries); + QString uniqueState(); + void setUniqueState(const QString &state); + Q_SIGNALS: void verificationReceived(QMap); void serverClosed(bool); // whether it has found parameters @@ -45,6 +48,7 @@ protected: int timeout_; int maxtries_; int tries_; + QString uniqueState_; }; #endif // O2REPLYSERVER_H diff --git a/external/o2/src/o2requestor.cpp b/external/o2/src/o2requestor.cpp index d737d3573af..181cf5c2507 100644 --- a/external/o2/src/o2requestor.cpp +++ b/external/o2/src/o2requestor.cpp @@ -1,5 +1,8 @@ +#include + #include #include +#include #if QT_VERSION >= 0x050000 #include #endif @@ -8,7 +11,7 @@ #include "o2.h" #include "o0globals.h" -O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent): QObject(parent), reply_(NULL), status_(Idle) { +O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent): QObject(parent), reply_(NULL), status_(Idle), addAccessTokenInQuery_(true), rawData_(false) { manager_ = manager; authenticator_ = authenticator; if (authenticator) { @@ -21,43 +24,162 @@ O2Requestor::O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObj O2Requestor::~O2Requestor() { } -int O2Requestor::get(const QNetworkRequest &req) { +void O2Requestor::setAddAccessTokenInQuery(bool value) { + addAccessTokenInQuery_ = value; +} + +void O2Requestor::setAccessTokenInAuthenticationHTTPHeaderFormat(const QString &value) { + accessTokenInAuthenticationHTTPHeaderFormat_ = value; +} + +int O2Requestor::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) { if (-1 == setup(req, QNetworkAccessManager::GetOperation)) { return -1; } reply_ = manager_->get(request_); - timedReplies_.add(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); + +#if QT_VERSION < 0x051500 connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); return id_; } -int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data) { +int O2Requestor::post(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) { if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { return -1; } + rawData_ = true; data_ = data; reply_ = manager_->post(request_, data_); - timedReplies_.add(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); +#if QT_VERSION < 0x051500 connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); return id_; } -int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data) { +int O2Requestor::post(const QNetworkRequest & req, QHttpMultiPart* data, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::PostOperation)) { + return -1; + } + rawData_ = false; + multipartData_ = data; + reply_ = manager_->post(request_, multipartData_); + multipartData_->setParent(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); +#if QT_VERSION < 0x051500 + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +int O2Requestor::put(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) { if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { return -1; } + rawData_ = true; data_ = data; reply_ = manager_->put(request_, data_); - timedReplies_.add(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); +#if QT_VERSION < 0x051500 connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); return id_; } +int O2Requestor::put(const QNetworkRequest & req, QHttpMultiPart* data, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::PutOperation)) { + return -1; + } + rawData_ = false; + multipartData_ = data; + reply_ = manager_->put(request_, multipartData_); + multipartData_->setParent(reply_); + timedReplies_.add(new O2Reply(reply_, timeout)); +#if QT_VERSION < 0x051500 + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +int O2Requestor::deleteResource(const QNetworkRequest & req, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::DeleteOperation)) { + return -1; + } + rawData_ = false; + reply_ = manager_->deleteResource(request_); + timedReplies_.add(new O2Reply(reply_, timeout)); +#if QT_VERSION < 0x051500 + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + return id_; +} + +int O2Requestor::customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, int timeout/* = 60*1000*/) +{ + (void)timeout; + + if (-1 == setup(req, QNetworkAccessManager::CustomOperation, verb)) { + return -1; + } + data_ = data; + QBuffer * buffer = new QBuffer; + buffer->setData(data_); + reply_ = manager_->sendCustomRequest(request_, verb, buffer); + buffer->setParent(reply_); + timedReplies_.add(new O2Reply(reply_)); +#if QT_VERSION < 0x051500 + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + return id_; +} + +int O2Requestor::head(const QNetworkRequest &req, int timeout/* = 60*1000*/) +{ + if (-1 == setup(req, QNetworkAccessManager::HeadOperation)) { + return -1; + } + reply_ = manager_->head(request_); + timedReplies_.add(new O2Reply(reply_, timeout)); +#if QT_VERSION < 0x051500 + connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif + connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); + return id_; +} + void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) { if (status_ != Requesting) { qWarning() << "O2Requestor::onRefreshFinished: No pending request"; @@ -72,15 +194,13 @@ void O2Requestor::onRefreshFinished(QNetworkReply::NetworkError error) { } void O2Requestor::onRequestFinished() { - QNetworkReply *senderReply = qobject_cast(sender()); - QNetworkReply::NetworkError error = senderReply->error(); if (status_ == Idle) { return; } - if (reply_ != senderReply) { + if (reply_ != qobject_cast(sender())) { return; } - if (error == QNetworkReply::NoError) { + if (reply_->error() == QNetworkReply::NoError) { QTimer::singleShot(10, this, SLOT(finish())); } } @@ -95,7 +215,6 @@ void O2Requestor::onRequestError(QNetworkReply::NetworkError error) { } int httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qWarning() << "O2Requestor::onRequestError: HTTP status" << httpStatus << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - qDebug() << reply_->readAll(); if ((status_ == Requesting) && (httpStatus == 401)) { // Call O2::refresh. Note the O2 instance might live in a different thread if (QMetaObject::invokeMethod(authenticator_, "refresh")) { @@ -115,12 +234,15 @@ void O2Requestor::onUploadProgress(qint64 uploaded, qint64 total) { if (reply_ != qobject_cast(sender())) { return; } + // Restart timeout because request in progress + O2Reply *o2Reply = timedReplies_.find(reply_); + if(o2Reply) + o2Reply->start(); Q_EMIT uploadProgress(id_, uploaded, total); } -int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation) { +int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) { static int currentId; - QUrl url; if (status_ != Idle) { qWarning() << "O2Requestor::setup: Another request pending"; @@ -130,15 +252,30 @@ int O2Requestor::setup(const QNetworkRequest &req, QNetworkAccessManager::Operat request_ = req; operation_ = operation; id_ = currentId++; - url_ = url = req.url(); + url_ = req.url(); + + QUrl url = url_; + if (addAccessTokenInQuery_) { #if QT_VERSION < 0x050000 - url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); #else - QUrlQuery query(url); - query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); - url.setQuery(query); + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); #endif + } + request_.setUrl(url); + + // If the service require the access token to be sent as a Authentication HTTP header, we add the access token. + if (!accessTokenInAuthenticationHTTPHeaderFormat_.isEmpty()) { + request_.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, accessTokenInAuthenticationHTTPHeaderFormat_.arg(authenticator_->token()).toLatin1()); + } + + if (!verb.isEmpty()) { + request_.setRawHeader(O2_HTTP_HTTP_HEADER, verb); + } + status_ = Requesting; error_ = QNetworkReply::NoError; return id_; @@ -155,7 +292,11 @@ void O2Requestor::finish() { timedReplies_.remove(reply_); reply_->disconnect(this); reply_->deleteLater(); + QList headers = reply_->rawHeaderPairs(); Q_EMIT finished(id_, error_, data); + Q_EMIT finished(id_, error_, reply_->errorString(), data); + Q_EMIT finished(id_, error_, data, headers); + Q_EMIT finished(id_, error_, reply_->errorString(), data, headers); } void O2Requestor::retry() { @@ -167,27 +308,56 @@ void O2Requestor::retry() { reply_->disconnect(this); reply_->deleteLater(); QUrl url = url_; + if (addAccessTokenInQuery_) { #if QT_VERSION < 0x050000 - url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); #else - QUrlQuery query(url); - query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); - url.setQuery(query); + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_ACCESS_TOKEN, authenticator_->token()); + url.setQuery(query); #endif + } request_.setUrl(url); + + // If the service require the access token to be sent as a Authentication HTTP header, + // we update the access token when retrying. + if(!accessTokenInAuthenticationHTTPHeaderFormat_.isEmpty()) { + request_.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, accessTokenInAuthenticationHTTPHeaderFormat_.arg(authenticator_->token()).toLatin1()); + } + status_ = ReRequesting; switch (operation_) { case QNetworkAccessManager::GetOperation: reply_ = manager_->get(request_); break; case QNetworkAccessManager::PostOperation: - reply_ = manager_->post(request_, data_); + reply_ = rawData_ ? manager_->post(request_, data_) : manager_->post(request_, multipartData_); + break; + case QNetworkAccessManager::CustomOperation: + { + QBuffer * buffer = new QBuffer; + buffer->setData(data_); + reply_ = manager_->sendCustomRequest(request_, request_.rawHeader(O2_HTTP_HTTP_HEADER), buffer); + buffer->setParent(reply_); + } + break; + case QNetworkAccessManager::PutOperation: + reply_ = rawData_ ? manager_->post(request_, data_) : manager_->put(request_, multipartData_); + break; + case QNetworkAccessManager::HeadOperation: + reply_ = manager_->head(request_); break; default: - reply_ = manager_->put(request_, data_); + assert(!"Unspecified operation for request"); + reply_ = manager_->get(request_); + break; } timedReplies_.add(reply_); +#if QT_VERSION < 0x051500 connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()), Qt::QueuedConnection); connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); } diff --git a/external/o2/src/o2requestor.h b/external/o2/src/o2requestor.h index caaca910459..c2ff20a9ff3 100644 --- a/external/o2/src/o2requestor.h +++ b/external/o2/src/o2requestor.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "o0export.h" #include "o2reply.h" @@ -20,24 +21,66 @@ class O0_EXPORT O2Requestor: public QObject { public: explicit O2Requestor(QNetworkAccessManager *manager, O2 *authenticator, QObject *parent = 0); ~O2Requestor(); + + + /// Some services require the access token to be sent as a Authentication HTTP header + /// and refuse requests with the access token in the query. + /// This function allows to use or ignore the access token in the query. + /// The default value of `true` means that the query will contain the access token. + /// By setting the value to false, the query will not contain the access token. + /// See: + /// https://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16#section-4.3 + /// https://tools.ietf.org/html/rfc6750#section-2.3 + + void setAddAccessTokenInQuery(bool value); + + /// Some services require the access token to be sent as a Authentication HTTP header. + /// This is the case for Twitch and Mixer. + /// When the access token expires and is refreshed, O2Requestor::retry() needs to update the Authentication HTTP header. + /// In order to do so, O2Requestor needs to know the format of the Authentication HTTP header. + void setAccessTokenInAuthenticationHTTPHeaderFormat(const QString &value); public Q_SLOTS: /// Make a GET request. /// @return Request ID or -1 if there are too many requests in the queue. - int get(const QNetworkRequest &req); + int get(const QNetworkRequest &req, int timeout = 60*1000); /// Make a POST request. /// @return Request ID or -1 if there are too many requests in the queue. - int post(const QNetworkRequest &req, const QByteArray &data); + int post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000); + int post(const QNetworkRequest &req, QHttpMultiPart* data, int timeout = 60*1000); /// Make a PUT request. /// @return Request ID or -1 if there are too many requests in the queue. - int put(const QNetworkRequest &req, const QByteArray &data); + int put(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000); + int put(const QNetworkRequest &req, QHttpMultiPart* data, int timeout = 60*1000); + + /// Make a DELETE request. + /// @return Request ID or -1 if there are too many requests in the queue. + int deleteResource(const QNetworkRequest &req, int timeout = 60*1000); + + /// Make a HEAD request. + /// @return Request ID or -1 if there are too many requests in the queue. + int head(const QNetworkRequest &req, int timeout = 60*1000); + + /// Make a custom request. + /// @return Request ID or -1 if there are too many requests in the queue. + int customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, int timeout = 60*1000); Q_SIGNALS: + /// Emitted when a request has been completed or failed. void finished(int id, QNetworkReply::NetworkError error, QByteArray data); + /// Emitted when a request has been completed or failed. + void finished(int id, QNetworkReply::NetworkError error, QString errorText, QByteArray data); + + /// Emitted when a request has been completed or failed. Also reply headers will be provided. + void finished(int id, QNetworkReply::NetworkError error, QByteArray data, QList headers); + + /// Emitted when a request has been completed or failed. Also reply headers will be provided. + void finished(int id, QNetworkReply::NetworkError error, QString errorText, QByteArray data, QList headers); + /// Emitted when an upload has progressed. void uploadProgress(int id, qint64 bytesSent, qint64 bytesTotal); @@ -61,7 +104,7 @@ protected Q_SLOTS: void onUploadProgress(qint64 uploaded, qint64 total); protected: - int setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation); + int setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray()); enum Status { Idle, Requesting, ReRequesting @@ -71,6 +114,7 @@ protected: O2 *authenticator_; QNetworkRequest request_; QByteArray data_; + QHttpMultiPart* multipartData_; QNetworkReply *reply_; Status status_; int id_; @@ -78,6 +122,9 @@ protected: QUrl url_; O2ReplyList timedReplies_; QNetworkReply::NetworkError error_; + bool addAccessTokenInQuery_; + QString accessTokenInAuthenticationHTTPHeaderFormat_; + bool rawData_; }; #endif // O2REQUESTOR_H diff --git a/external/o2/src/o2simplecrypt.cpp b/external/o2/src/o2simplecrypt.cpp index 38e250c5dee..3db2b145ef1 100644 --- a/external/o2/src/o2simplecrypt.cpp +++ b/external/o2/src/o2simplecrypt.cpp @@ -32,7 +32,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include O0SimpleCrypt::O0SimpleCrypt(): m_key(0), @@ -40,6 +39,11 @@ O0SimpleCrypt::O0SimpleCrypt(): m_protectionMode(ProtectionChecksum), m_lastError(ErrorNoError) { +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#else + m_rand.seed(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#endif } O0SimpleCrypt::O0SimpleCrypt(quint64 key): @@ -48,6 +52,11 @@ O0SimpleCrypt::O0SimpleCrypt(quint64 key): m_protectionMode(ProtectionChecksum), m_lastError(ErrorNoError) { +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#else + m_rand.seed(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#endif splitKey(); } @@ -103,7 +112,11 @@ QByteArray O0SimpleCrypt::encryptToByteArray(QByteArray plaintext) if (m_protectionMode == ProtectionChecksum) { flags |= CryptoFlagChecksum; QDataStream s(&integrityProtection, QIODevice::WriteOnly); +#if QT_VERSION >= 0x060000 + s << qChecksum(QByteArrayView(ba)); +#else s << qChecksum(ba.constData(), ba.length()); +#endif } else if (m_protectionMode == ProtectionHash) { flags |= CryptoFlagHash; QCryptographicHash hash(QCryptographicHash::Sha1); @@ -113,7 +126,11 @@ QByteArray O0SimpleCrypt::encryptToByteArray(QByteArray plaintext) } //prepend a random char to the string - char randomChar = char(QRandomGenerator::global()->generate() & 0xFF); +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + char randomChar = char(qrand() & 0xFF); +#else + char randomChar = char(m_rand.generate() & 0xFF); +#endif ba = randomChar + integrityProtection + ba; int pos(0); @@ -227,7 +244,11 @@ QByteArray O0SimpleCrypt::decryptToByteArray(QByteArray cypher) s >> storedChecksum; } ba = ba.mid(2); +#if QT_VERSION >= 0x060000 + quint16 checksum = qChecksum(QByteArrayView(ba)); +#else quint16 checksum = qChecksum(ba.constData(), ba.length()); +#endif integrityOk = (checksum == storedChecksum); } else if (flags.testFlag(CryptoFlagHash)) { if (ba.length() < 20) { diff --git a/external/o2/src/o2skydrive.cpp b/external/o2/src/o2skydrive.cpp index a458c78bdd5..b8e1f7bba62 100644 --- a/external/o2/src/o2skydrive.cpp +++ b/external/o2/src/o2skydrive.cpp @@ -85,7 +85,11 @@ void O2Skydrive::redirected(const QUrl &url) { QNetworkReply *tokenReply = manager_->post(tokenRequest, data); timedReplies_.add(tokenReply); connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif } else { // Get access token QString urlToken = ""; diff --git a/external/o2/src/o2surveymonkey.cpp b/external/o2/src/o2surveymonkey.cpp index 8eb3613a0c1..0f1b4a297dc 100644 --- a/external/o2/src/o2surveymonkey.cpp +++ b/external/o2/src/o2surveymonkey.cpp @@ -7,7 +7,6 @@ #endif #include "o2surveymonkey.h" -#include "o2globals.h" static const char *SMEndpoint = "https://api.surveymonkey.net/oauth/authorize"; static const char *SMTokenUrl = "https://api.surveymonkey.net/oauth/token"; diff --git a/external/o2/src/o2uber.cpp b/external/o2/src/o2uber.cpp new file mode 100644 index 00000000000..ea8ffe69e54 --- /dev/null +++ b/external/o2/src/o2uber.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif + +#include "o2uber.h" +#include "o0globals.h" + +static const char *UberEndpoint = "https://login.uber.com/oauth/v2/authorize"; +static const char *UberTokenUrl = "https://login.uber.com/oauth/v2/token"; +static const char *UberExpiresIn = "expires_in"; +static const char *UberGrantType = "authorization_code"; + +O2Uber::O2Uber(QObject *parent): O2(parent) +{ + setRequestUrl(UberEndpoint); + setTokenUrl(UberTokenUrl); +} + +void O2Uber::onVerificationReceived(const QMap response){ + + qDebug() << "O2Uber::onVerificationReceived: Emitting closeBrowser()"; + Q_EMIT closeBrowser(); + + if (response.contains("error")) { + qWarning() << "O2Uber::onVerificationReceived: Verification failed"; + foreach (QString key, response.keys()) { + qWarning() << "O2Uber::onVerificationReceived:" << key << response.value(key); + } + Q_EMIT linkingFailed(); + return; + } + + // Save access code + setCode(response.value(O2_OAUTH2_GRANT_TYPE_CODE)); + + // Exchange access code for access/refresh tokens + QUrl url(tokenUrl_); +#if QT_VERSION < 0x050000 + url.addQueryItem(O2_OAUTH2_CLIENT_ID, clientId_); + url.addQueryItem(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + url.addQueryItem(O2_OAUTH2_GRANT_TYPE, UberGrantType); + url.addQueryItem(O2_OAUTH2_REDIRECT_URI, redirectUri_); + url.addQueryItem(O2_OAUTH2_GRANT_TYPE_CODE, code()); + url.addQueryItem(O2_OAUTH2_SCOPE, scope_); +#else + QUrlQuery query(url); + query.addQueryItem(O2_OAUTH2_CLIENT_ID, clientId_); + query.addQueryItem(O2_OAUTH2_CLIENT_SECRET, clientSecret_); + query.addQueryItem(O2_OAUTH2_GRANT_TYPE, UberGrantType); + query.addQueryItem(O2_OAUTH2_REDIRECT_URI, redirectUri_); + query.addQueryItem(O2_OAUTH2_GRANT_TYPE_CODE, code()); + query.addQueryItem(O2_OAUTH2_SCOPE, scope_); + url.setQuery(query); +#endif + + QNetworkRequest tokenRequest(url); + tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkReply *tokenReply = manager_->post(tokenRequest, QByteArray()); + timedReplies_.add(tokenReply); + connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); +#if QT_VERSION < 0x051500 + connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#else + connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); +#endif +} + +void O2Uber::onTokenReplyFinished(){ + qDebug() << "O2Uber::onTokenReplyFinished"; + + QNetworkReply *tokenReply = qobject_cast(sender()); + if (tokenReply->error() == QNetworkReply::NoError) { + // Process reply + QByteArray replyData = tokenReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(replyData); + const QJsonObject rootObject = doc.object(); + + QVariantMap reply; + for (const QString &key : rootObject.keys()) { + reply.insert(key, rootObject[key].toVariant()); + } + + // Interpret reply + setToken(reply.value(O2_OAUTH2_ACCESS_TOKEN, QString()).toString()); + setExpires(reply.value(UberExpiresIn).toInt()); + setRefreshToken(reply.value(O2_OAUTH2_REFRESH_TOKEN, QString()).toString()); + setExtraTokens(reply); + timedReplies_.remove(tokenReply); + setLinked(true); + Q_EMIT linkingSucceeded(); + } else { + qWarning() << "O2Uber::onTokenReplyFinished:" << tokenReply->errorString(); + } +} diff --git a/external/o2/src/o2uber.h b/external/o2/src/o2uber.h new file mode 100644 index 00000000000..3d90260620e --- /dev/null +++ b/external/o2/src/o2uber.h @@ -0,0 +1,20 @@ +#ifndef O2UBER_H +#define O2UBER_H + +#include "o0export.h" +#include "o2.h" + +class O0_EXPORT O2Uber: public O2{ + Q_OBJECT + +public: + O2Uber(QObject *parent = 0); + +public Q_SLOTS: + void onVerificationReceived(QMap); + +protected Q_SLOTS: + virtual void onTokenReplyFinished(); +}; + +#endif // O2UBER_H diff --git a/external/o2/src/o2vimeo.cpp b/external/o2/src/o2vimeo.cpp new file mode 100644 index 00000000000..806b3f0d942 --- /dev/null +++ b/external/o2/src/o2vimeo.cpp @@ -0,0 +1,13 @@ +#include "o2vimeo.h" + +// Vimeo supported scopes: https://developer.vimeo.com/api/authentication#supported-scopes +static const char *VimeoScope = "public"; +static const char *VimeoEndpoint = "https://api.vimeo.com/oauth/authorize"; +static const char *VimeoTokenUrl = "https://api.vimeo.com/oauth/access_token"; + +O2Vimeo::O2Vimeo(QObject *parent): O2(parent) { + setRequestUrl(VimeoEndpoint); + setTokenUrl(VimeoTokenUrl); + setRefreshTokenUrl(VimeoTokenUrl); + setScope(VimeoScope); +} diff --git a/external/o2/src/o2vimeo.h b/external/o2/src/o2vimeo.h new file mode 100644 index 00000000000..f1acd164ffa --- /dev/null +++ b/external/o2/src/o2vimeo.h @@ -0,0 +1,15 @@ +#ifndef O2VIMEO_H +#define O2VIMEO_H + +#include "o0export.h" +#include "o2.h" + +/// Vimeo dialect of OAuth 2.0 +class O0_EXPORT O2Vimeo : public O2 { + Q_OBJECT + +public: + explicit O2Vimeo(QObject *parent = 0); +}; + +#endif // O2VIMEO_H diff --git a/external/o2/src/oxtwitter.cpp b/external/o2/src/oxtwitter.cpp index 777dc5e718c..b0768b9c30d 100644 --- a/external/o2/src/oxtwitter.cpp +++ b/external/o2/src/oxtwitter.cpp @@ -51,7 +51,11 @@ void OXTwitter::link() { oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, O2_SIGNATURE_TYPE_HMAC_SHA1)); oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); +#if QT_VERSION >= 0x050800 + oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1())); +#else oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); +#endif oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, QByteArray(""))); oauthParams.append(O0RequestParameter(O2_OAUTH_VERFIER, QByteArray(""))); @@ -61,9 +65,13 @@ void OXTwitter::link() { // Post request QNetworkRequest request(accessTokenUrl()); - request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); + decorateRequest(request, oauthParams); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, createQueryParameters(xAuthParams_)); +#if QT_VERSION < 0x051500 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); +#else + connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); +#endif connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); } diff --git a/external/o2/src/src.pri b/external/o2/src/src.pri index 98fe764d883..10be4102d4f 100644 --- a/external/o2/src/src.pri +++ b/external/o2/src/src.pri @@ -1,17 +1,19 @@ QT *= network -# script module is deprecated since Qt 5.5 (http://wiki.qt.io/New-Features-in-Qt-5.5) -!qtHaveModule(qml): QT *= script -qtHaveModule(qml): QT *= qml +lessThan(QT_MAJOR_VERSION, 5): QT *= script INCLUDEPATH += $$PWD SOURCES += \ + $$PWD/o0jsonresponse.cpp \ $$PWD/o1.cpp \ $$PWD/o1requestor.cpp \ + $$PWD/o1smugmug.cpp \ $$PWD/o1timedreply.cpp \ $$PWD/o2.cpp \ $$PWD/o2facebook.cpp \ $$PWD/o2gft.cpp \ + $$PWD/o2outlook.cpp \ + $$PWD/o2pollserver.cpp \ $$PWD/o2reply.cpp \ $$PWD/o2replyserver.cpp \ $$PWD/o2requestor.cpp \ @@ -20,18 +22,28 @@ SOURCES += \ $$PWD/o2simplecrypt.cpp \ $$PWD/o0baseauth.cpp \ $$PWD/o0settingsstore.cpp \ - $$PWD/o2spotify.cpp + $$PWD/o2spotify.cpp \ + $$PWD/o2google.cpp \ + $$PWD/o2googledevice.cpp \ + $$PWD/o2uber.cpp \ + $$PWD/o2msgraph.cpp HEADERS += \ + $$PWD/o0export.h \ + $$PWD/o0jsonresponse.h \ $$PWD/o1.h \ $$PWD/o1dropbox.h \ $$PWD/o1flickr.h \ $$PWD/o1requestor.h \ + $$PWD/o1smugmug.h \ $$PWD/o1twitter.h \ $$PWD/o1timedreply.h \ + $$PWD/o1upwork.h \ $$PWD/o2.h \ $$PWD/o2facebook.h \ $$PWD/o2gft.h \ + $$PWD/o2outlook.h \ + $$PWD/o2pollserver.h \ $$PWD/o2reply.h \ $$PWD/o2replyserver.h \ $$PWD/o2requestor.h \ @@ -44,4 +56,41 @@ HEADERS += \ $$PWD/o0requestparameter.h \ $$PWD/o0abstractstore.h \ $$PWD/o0settingsstore.h \ - $$PWD/o2spotify.h + $$PWD/o2spotify.h \ + $$PWD/o2google.h \ + $$PWD/o2googledevice.h \ + $$PWD/o2uber.h \ + $$PWD/o2msgraph.h + +headers.files += \ + $$PWD/o0jsonresponse.h \ + $$PWD/o1.h \ + $$PWD/o1dropbox.h \ + $$PWD/o1flickr.h \ + $$PWD/o1requestor.h \ + $$PWD/o1smugmug.h \ + $$PWD/o1twitter.h \ + $$PWD/o1timedreply.h \ + $$PWD/o1upwork.h \ + $$PWD/o2.h \ + $$PWD/o2facebook.h \ + $$PWD/o2gft.h \ + $$PWD/o2pollserver.h \ + $$PWD/o2reply.h \ + $$PWD/o2replyserver.h \ + $$PWD/o2requestor.h \ + $$PWD/o2skydrive.h \ + $$PWD/oxtwitter.h \ + $$PWD/o1freshbooks.h \ + $$PWD/o0baseauth.h \ + $$PWD/o0globals.h \ + $$PWD/o0simplecrypt.h \ + $$PWD/o0requestparameter.h \ + $$PWD/o0abstractstore.h \ + $$PWD/o0settingsstore.h \ + $$PWD/o2spotify.h \ + $$PWD/o2google.h \ + $$PWD/o2googledevice.h \ + $$PWD/o2uber.h \ + $$PWD/o2msgraph.h \ + $$PWD/o2outlook.h diff --git a/src/auth/oauth2/CMakeLists.txt b/src/auth/oauth2/CMakeLists.txt index 15b3fb2588d..15cf48ea501 100644 --- a/src/auth/oauth2/CMakeLists.txt +++ b/src/auth/oauth2/CMakeLists.txt @@ -18,9 +18,11 @@ endif() if(WITH_INTERNAL_O2) set(O2_SRCS ${O2_SOURCE_DIR}/o0baseauth.cpp + ${O2_SOURCE_DIR}/o0jsonresponse.cpp ${O2_SOURCE_DIR}/o0keychainstore.cpp ${O2_SOURCE_DIR}/o0settingsstore.cpp ${O2_SOURCE_DIR}/o2.cpp + ${O2_SOURCE_DIR}/o2pollserver.cpp ${O2_SOURCE_DIR}/o2reply.cpp ${O2_SOURCE_DIR}/o2replyserver.cpp ${O2_SOURCE_DIR}/o2requestor.cpp @@ -28,10 +30,12 @@ if(WITH_INTERNAL_O2) ) set(O2_HDRS ${O2_INCLUDE_DIR}/o0abstractstore.h + ${O2_INCLUDE_DIR}/o0jsonresponse.h ${O2_INCLUDE_DIR}/o0baseauth.h ${O2_INCLUDE_DIR}/o0export.h ${O2_INCLUDE_DIR}/o0globals.h ${O2_INCLUDE_DIR}/o0keychainstore.h + ${O2_INCLUDE_DIR}/o2pollserver.h ${O2_INCLUDE_DIR}/o0requestparameter.h ${O2_INCLUDE_DIR}/o0settingsstore.h ${O2_INCLUDE_DIR}/o0simplecrypt.h