Re-sync external o2 library

Keep a few downstream patches:

- Oauth2 PKCE initial support (76df15690c5b3e7fd2f97177cd8e743546821f07)
- Find QtKeychain by its cmake target
  (b9e859f0ed1d9d783c0a7b4f28e3d73ac21572f6)
- Allow compiling o2 with Qt6 (6fbe9a4fb3a29bbbd32987153303181d3d4362cd)
- Hide more debug noise (1f91895b1e8d4ca8dd2bb24b0ea35f5bcb425a96)
- If an o2 auth refresh reply contains an error message, then
the refresh was NOT successful
(9af5a531b1a9c4b452b98c0a10387aefa850edee)
- Ensure correct thread locale QgsNetworkAccessManager is used during
o2 requests (9cde65457b3717a74e32277c843a2945eb4f1221)
This commit is contained in:
Nyall Dawson 2024-11-21 09:44:11 +10:00
parent 725cb445e7
commit 806bbb2bfb
51 changed files with 1740 additions and 310 deletions

211
external/o2/README.md vendored
View File

@ -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<O1RequestParameter> requestParams = QList<O1RequestParameter>();
requestParams << O1RequestParameter(paramName, tweetText);
QList<O1RequestParameter> requestParams = QList<O1RequestParameter>();
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<O1RequestParameter>(), data);
QNetworkReply *reply = requestor->post(request, QList<O1RequestParameter>(), 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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -1,16 +1,19 @@
#include <QDataStream>
#include <QDebug>
#include <QIODevice>
#include <QUrlQuery>
#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<O0RequestParameter> &parameters) {
QByteArray ret;
bool first = true;

View File

@ -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:<localPort>/".
/// 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

View File

@ -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

41
external/o2/src/o0jsonresponse.cpp vendored Normal file
View File

@ -0,0 +1,41 @@
#include "o0jsonresponse.h"
#include <QByteArray>
#include <QDebug>
#if QT_VERSION >= 0x050000
#include <QJsonDocument>
#include <QJsonObject>
#else
#include <QScriptEngine>
#include <QScriptValueIterator>
#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
}

11
external/o2/src/o0jsonresponse.h vendored Normal file
View File

@ -0,0 +1,11 @@
#ifndef O0JSONRESPONSE_H
#define O0JSONRESPONSE_H
#include <QVariantMap>
class QByteArray;
/// Parse JSON data into a QVariantMap
QVariantMap parseJsonResponse(const QByteArray &data);
#endif // O0JSONRESPONSE_H

View File

@ -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( <password data> )
// 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;
}

View File

@ -8,21 +8,48 @@
#include "o0abstractstore.h"
#include <QString>
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 <keychain.h> 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 <keychain.h>.
/// 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<QString,QString> pairs_;

View File

@ -3,6 +3,8 @@
#include "o0export.h"
#include <QByteArray>
/// Request parameter (name-value pair) participating in authentication.
struct O0_EXPORT O0RequestParameter {
O0RequestParameter(const QByteArray &n, const QByteArray &v): name(n), value(v) {}

View File

@ -1,5 +1,6 @@
#include <QCryptographicHash>
#include <QByteArray>
#include <QDebug>
#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?";
}
}
}

View File

@ -30,7 +30,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QString>
#include <QVector>
#include <QFlags>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#include <QRandomGenerator>
#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)

View File

@ -6,6 +6,7 @@
#include <QDebug>
#include <QDataStream>
#include <QStringList>
#include <algorithm>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
@ -15,20 +16,32 @@
#include <QMessageAuthenticationCode>
#endif
#if QT_VERSION >= 0x051500
#include <QRandomGenerator>
#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>("QNetworkReply::NetworkError");
connect(replyServer_, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
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<O0RequestParameter> &oauthParams, cons
// Append a sorted+encoded list of all request parameters to the base string
QList<O0RequestParameter> 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<O0RequestParameter> &oauthParams, const QList<O0
QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op);
QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret);
#if QT_VERSION >= 0x050100
return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha1).toBase64();
return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha256).toBase64();
#else
return hmacSha1(secret, baseString);
#endif
@ -161,7 +174,7 @@ QByteArray O1::buildAuthorizationHeader(const QList<O0RequestParameter> &oauthPa
bool first = true;
QByteArray ret("OAuth ");
QList<O0RequestParameter> 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<O0RequestParameter> &oauthPa
return ret;
}
void O1::decorateRequest(QNetworkRequest &req, const QList<O0RequestParameter> &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<O0RequestParameter> headers, const QNetworkRequest &req, const QList<O0RequestParameter> &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<O0RequestParameter> 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<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
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<O0RequestParameter> 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<O0RequestParameter> 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<O0RequestParameter>(), 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<QString, QString> 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();
}

22
external/o2/src/o1.h vendored
View File

@ -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<O0RequestParameter> requestParameters READ requestParameters WRITE setRequestParameters);
Q_PROPERTY(QList<O0RequestParameter> requestParameters READ requestParameters WRITE setRequestParameters)
QList<O0RequestParameter> requestParameters();
void setRequestParameters(const QList<O0RequestParameter> &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<QString, QString> parseResponse(const QByteArray &response);
@ -56,6 +62,9 @@ public:
/// Build the value of the "Authorization:" header.
static QByteArray buildAuthorizationHeader(const QList<O0RequestParameter> &oauthParams);
/// Add common configuration (headers) to @p req.
void decorateRequest(QNetworkRequest &req, const QList<O0RequestParameter> &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<QString,QString> 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<O0RequestParameter> requestParameters_;
QString callbackUrl_;
@ -119,7 +130,6 @@ protected:
QString verifier_;
QString signatureMethod_;
QNetworkAccessManager *manager_;
O2ReplyServer *replyServer_;
};
#endif // O1_H

View File

@ -45,13 +45,17 @@ QNetworkRequest O1Requestor::setup(const QNetworkRequest &req, const QList<O0Req
oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, authenticator_->token().toLatin1()));
oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, authenticator_->signatureMethod().toLatin1()));
oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, O1::nonce()));
#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;
}

86
external/o2/src/o1smugmug.cpp vendored Normal file
View File

@ -0,0 +1,86 @@
#include <cstddef>
#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<std::size_t>(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<std::size_t>(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"));
}

69
external/o2/src/o1smugmug.h vendored Normal file
View File

@ -0,0 +1,69 @@
#ifndef O1SMUGMUG_H
#define O1SMUGMUG_H
#include "o0export.h"
#include "o1.h"
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#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

18
external/o2/src/o1upwork.h vendored Normal file
View File

@ -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

326
external/o2/src/o2.cpp vendored
View File

@ -10,52 +10,25 @@
#include <QCryptographicHash>
#include <QTimer>
#include <QVariantMap>
#include <QUuid>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
#endif
#if QT_VERSION >= 0x050000
#include <QRegularExpression>
#else
#include <QScriptEngine>
#include <QScriptValueIterator>
#include <QRegExp>
#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<QPair<QString, QString> > parameters) {
#if QT_VERSION < 0x050000
@ -67,14 +40,30 @@ static void addQueryParametersToUrl(QUrl &url, QList<QPair<QString, QString> >
#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>("QNetworkReply::NetworkError");
connect(replyServer_, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
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<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
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<QPair<QString, QString> > 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<O0RequestParameter> 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<QString, QString> response) {
//qDebug() << "O2::onVerificationReceived:" << response;
//qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()";
Q_EMIT closeBrowser();
@ -271,24 +329,31 @@ void O2::onVerificationReceived(const QMap<QString, QString> 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<QNetworkReply *>(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 &params)
{
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<O0RequestParameter> 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<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
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<QNetworkReply *>(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<QNetworkReply *>(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();
}

30
external/o2/src/o2.h vendored
View File

@ -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<QString, QString>);
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<QString, QString> &parameters);
@ -163,6 +167,8 @@ protected:
void setExpires(int v);
virtual QNetworkAccessManager *getManager();
/// Start polling authorization server
void startPollServer(const QVariantMap &params);
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

View File

@ -1,4 +1,6 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>
#include <QNetworkReply>
#include <QString>
@ -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<QString, QString> 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

View File

@ -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<QString, QString>);
protected Q_SLOTS:
virtual void onTokenReplyFinished();
};

View File

@ -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);

View File

@ -5,6 +5,7 @@
#ifndef O2_O2GOOGLE_H
#define O2_O2GOOGLE_H
#include "o2.h"
class O2Google : public O2 {
Q_OBJECT

15
external/o2/src/o2googledevice.cpp vendored Normal file
View File

@ -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);
}

16
external/o2/src/o2googledevice.h vendored Normal file
View File

@ -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

View File

@ -1,5 +1,4 @@
#include "o2hubic.h"
#include "o2globals.h"
#include "o2replyserver.h"
#include <QHostAddress>

10
external/o2/src/o2msgraph.cpp vendored Normal file
View File

@ -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);
}

15
external/o2/src/o2msgraph.h vendored Normal file
View File

@ -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

14
external/o2/src/o2outlook.cpp vendored Normal file
View File

@ -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_;
}

17
external/o2/src/o2outlook.h vendored Normal file
View File

@ -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

117
external/o2/src/o2pollserver.cpp vendored Normal file
View File

@ -0,0 +1,117 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "o2pollserver.h"
#include "o0jsonresponse.h"
static QMap<QString, QString> toVerificationParams(const QVariantMap &map)
{
QMap<QString, QString> 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<QNetworkReply *>(sender());
if (!reply) {
qDebug() << "O2PollServer::onReplyFinished: reply is null";
return;
}
QByteArray replyData = reply->readAll();
QMap<QString, QString> 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();
}

49
external/o2/src/o2pollserver.h vendored Normal file
View File

@ -0,0 +1,49 @@
#ifndef O2POLLSERVER_H
#define O2POLLSERVER_H
#include <QByteArray>
#include <QMap>
#include <QNetworkRequest>
#include <QObject>
#include <QString>
#include <QTimer>
#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<QString, QString>);
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

View File

@ -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);
}

View File

@ -29,7 +29,7 @@ public:
};
/// List of O2Replies.
class O2ReplyList {
class O0_EXPORT O2ReplyList {
public:
O2ReplyList() { ignoreSslErrors_ = false; }

View File

@ -12,6 +12,7 @@
#include <QUrlQuery>
#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;
}

View File

@ -30,6 +30,9 @@ public:
int callbackTries();
void setCallbackTries(int maxtries);
QString uniqueState();
void setUniqueState(const QString &state);
Q_SIGNALS:
void verificationReceived(QMap<QString, QString>);
void serverClosed(bool); // whether it has found parameters
@ -45,6 +48,7 @@ protected:
int timeout_;
int maxtries_;
int tries_;
QString uniqueState_;
};
#endif // O2REPLYSERVER_H

View File

@ -1,5 +1,8 @@
#include <cassert>
#include <QDebug>
#include <QTimer>
#include <QBuffer>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#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<QNetworkReply *>(sender());
QNetworkReply::NetworkError error = senderReply->error();
if (status_ == Idle) {
return;
}
if (reply_ != senderReply) {
if (reply_ != qobject_cast<QNetworkReply *>(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<QNetworkReply *>(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<QNetworkReply::RawHeaderPair> 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)));
}

View File

@ -7,6 +7,7 @@
#include <QNetworkAccessManager>
#include <QUrl>
#include <QByteArray>
#include <QHttpMultiPart>
#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<QNetworkReply::RawHeaderPair> 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<QNetworkReply::RawHeaderPair> 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

View File

@ -32,7 +32,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QCryptographicHash>
#include <QDataStream>
#include <QIODevice>
#include <QRandomGenerator>
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) {

View File

@ -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 = "";

View File

@ -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";

102
external/o2/src/o2uber.cpp vendored Normal file
View File

@ -0,0 +1,102 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>
#include <QNetworkReply>
#include <QString>
#include <QStringList>
#include <QUrl>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#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<QString, QString> 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<QNetworkReply *>(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();
}
}

20
external/o2/src/o2uber.h vendored Normal file
View File

@ -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<QString, QString>);
protected Q_SLOTS:
virtual void onTokenReplyFinished();
};
#endif // O2UBER_H

13
external/o2/src/o2vimeo.cpp vendored Normal file
View File

@ -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);
}

15
external/o2/src/o2vimeo.h vendored Normal file
View File

@ -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

View File

@ -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()));
}

View File

@ -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

View File

@ -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