My objective
To exchange refresh token for access token from google using OAuth 2.
My code
bool Google_Account::Refresh_Access_Token_Using_Refresh_Token()
{
// Prepare Url
QUrl url(tr("https://www.googleapis.com/oauth2/v4/token"));
// Create request
QNetworkRequest request(url);
request.setRawHeader("Host:","www.googleapis.com");
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// Create request body ClientID, ClientSecret, RefreshTokenString are class data members
QString RequestBody = tr("client_secret=%1&").arg(ClientSecret) +
tr("grant_type=refresh_token&")+
tr("refresh_token=%1&").arg(RefreshTokenString)+
tr("client_id=%1").arg(ClientID);
QByteArray array = RequestBody.toUtf8();
// Get reply
QNetworkReply *reply = mQNAM.post(request, array); // mQNAM is QNetworkAccessManager
// Set timeout to reply while waiting for reply finished
bool stop = false;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, [&](){
qDebug()<<"Time out";
stop = true;
});
timer.start(5000);
// Wait till the response is completed
while(!reply->isFinished()){
QCoreApplication::processEvents();
if(stop){
qDebug()<<"Going to abort";
reply->abort();
}
}
// Check for reply
if(reply->isFinished()){
if(reply->error() != QNetworkReply::NoError){
qDebug()<<reply->readAll();
emit setMessage("Error: "+reply->errorString());
delete reply;
return false;
}
else{
QByteArray array = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(array);
QJsonObject obj = document.object();
access_token = obj.value("access_token").toString(); //access_token is class data variable
delete reply;
return true;
}
}
else{
delete reply;
return false;
}
}
The problem is that if I run this code in my windows 7 pc(Qt 5.11.1) everything is fine I get the access token but if i run in my raspberry pi(raspbian Qt 5.7) I get Error 400, Bad request from google. I tried using the access_token got from my windows pc and made other request such as to get the file list from google drive, they are working fine in raspberry, but only this I am having problem. What am I doing wrong?
P.S the code is refactored to the specific details only, in reality I am getting the client id and other keys from QSettings
This smells like you're getting a 400 code when Qt is requesting a token, because the login code you get when the flow returns to your app is URL-encoded. We wrote about How To Authenticate with Google SSO in Qt with some code samples including this bit that injects a parameter modifier in the flow:
google->setModifyParametersFunction([](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
// Percent-decode the "code" parameter so Google can match it
if (stage == QAbstractOAuth::Stage::RequestingAccessToken) {
QByteArray code = parameters->value("code").toByteArray();
(*parameters)["code"] = QUrl::fromPercentEncoding(code);
}
});
Do that before the initial request, but as suggested by #Giancarlo above, I'd rewrite your code to work asynchronously. It's simpler, and more reliable.
Related
From this webpage, I have code.
When I use "http://httpbin.org/get", everything is OK.
But when I use my own url, for example "http://my-json-server.typicode.com/typicode/demo/db", I'm getting an error:
Unable to retrieve request headers
Where is my fault?
void RequestHeaders::getRequest()
{
//const QUrl url("http://httpbin.org/get"); // OK
const QUrl url("http://my-json-server.typicode.com/typicode/demo/db"); // Not OK
QNetworkRequest request(url);
QNetworkReply* reply = m_networkAccessManager->get(request);
bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onGetReply()));
Q_ASSERT(ok);
Q_UNUSED(ok);
}
void RequestHeaders::onGetReply()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QString response;
const QByteArray buffer(reply->readAll());
bb::data::JsonDataAccess ja;
const QVariant jsonva = ja.loadFromBuffer(buffer);
const QMap<QString, QVariant> jsonreply = jsonva.toMap();
QMap<QString, QVariant>::const_iterator it = jsonreply.find("headers");
if (it != jsonreply.end()) {
const QMap<QString, QVariant> headers = it.value().toMap();
for (QMap<QString, QVariant>::const_iterator hdrIter = headers.begin();
hdrIter != headers.end(); ++hdrIter) {
if (hdrIter.value().toString().trimmed().isEmpty())
continue;
response += QString::fromLatin1("%1: %2\r\n").arg(hdrIter.key(),
hdrIter.value().toString());
}
}
for (it = jsonreply.begin(); it != jsonreply.end(); it++) {
if (it.value().toString().trimmed().isEmpty())
continue;
response += QString::fromLatin1("%1: %2\r\n").arg(it.key(), it.value().toString());
}
reply->deleteLater();
if (response.trimmed().isEmpty()) {
response = tr("Unable to retrieve request headers");
}
emit complete(response);
}
Your onGetReply handler is parsing the server's HTTP response body as JSON, searching it for a "headers" child field, and if found then extracts that child's own child fields into a local response variable.
http://httpbin.org/get responds with a JSON object containing a "headers" child object that has child fields in it. So your response variable ends up not being empty.
http://my-json-server.typicode.com/typicode/demo/db responds with a JSON object that does not contain any "headers" child. So your response variable is left empty.
You need to either:
fix your server to respond with JSON that actually matches what your code is expecting.
fix your onGetReply() code to handle the JSON that your server is actually sending.
My program is getting data from mysql table via json format through php script, that generating json.
My function sends to server post request (with some params) and getting response. With that all okay. Works fine.
But, when I want to get specified cell (data from json array), it takes empty string (program works fast and taking data from a string before it sets up)
Here's the code with post request:
void dbase::requestor(QString option)
{
curr_js = ""; //nulling string
QString urla = host+"transfer.php";
// /////////
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QUrl url(urla);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery params;
params.addQueryItem("api", key);
params.addQueryItem("option", option);
//QEventLoop loop;
connect(manager, SIGNAL(finished(QNetworkReply *)),this, SLOT(replyFinished(QNetworkReply *)));
//loop.exec(); tryed eventloop, but bcuz of this my replyFinished is not accessible. error code under this one
manager->post(request, params.query().toUtf8());
}
If using eventloop
QObject::connect: No such slot QEventLoop::replyFinished(QNetworkReply *)
replyFinished function (setting up variable)
void dbase::replyFinished(QNetworkReply *reply)
{
curr_js = reply->readAll();
}
Usage (where is problem)
QString req = "SOME REQUEST";
database.requestor(req);
if (database.db_cell (0,0) == "")
{
qDebug()<<database.db_cell (0,0) << " - EMPTY - "<<database.curr_js;
}
So in this case I'm getting
"" - EMPTY - ""
But if I getting data from string (created button for test), it's there:
{"0":["1", "Admin","hashpwd"],"1":["2", "Albert","hashpwd"]}
db_cell function
QString dbase::db_cell (int indexrow, int indexcols)
{
QJsonDocument sd = QJsonDocument::fromJson(curr_js.toUtf8());
QJsonObject setobj = sd.object();
QJsonValue qqq = setobj.value(QString(QString::number(indexrow))).toArray()[indexcols];
return qqq.toString();
}
As I see, problem is that program need to wait before getting a data from json-string.
I am able to get the access token and refresh token but when I try to make a POST request to upload a file to Google Drive I get a 401 error. I believe I haven't set the body of my request correctly to upload the file (still not 100% sure how to do that), but if that were the issue I would expect a 400 error.
I have tried the instructions provided here https://airbrake.io/blog/http-errors/401-unauthorized-error like clearing browser cache and logging in/out of account. I have also made sure I am using the correct URL that google lists in their documentation. My first method is responsible for getting the access token. The second handles the uploads.
void
googleDriveInterface::getAuthentication() {
google = new QOAuth2AuthorizationCodeFlow;
google->setScope("https://www.googleapis.com/auth/drive");
QObject::connect(google, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); //this connection opens authorization URL in user's default browser
QJsonDocument credentials = getCredentials("googleDriveCredentials.json"); //json file data is loaded into credentials
//parse JSON
const auto object = credentials.object();
const auto settingsObject = object["installed"].toObject();
const QUrl authUri(settingsObject["auth_uri"].toString());
const auto clientID = settingsObject["client_id"].toString();
const QUrl tokenUri(settingsObject["token_uri"].toString());
const auto clientSecret(settingsObject["client_secret"].toString());
const auto redirectUris = settingsObject["redirect_uris"].toArray();
const QUrl redirectUri(redirectUris[0].toString()); //get first URI
const auto port = static_cast<quint16>(redirectUri.port()); // port needed to for QOAuthHttpServerReplyHandler
google->setAuthorizationUrl(authUri);
google->setClientIdentifier(clientID);
google->setAccessTokenUrl(tokenUri);
google->setClientIdentifierSharedKey(clientSecret);
auto replyHandler = new QOAuthHttpServerReplyHandler(port, this);
google->setReplyHandler(replyHandler);
QObject::connect(google, SIGNAL(granted()), this, SLOT(slotSetAuthToken())); //save token when access is granted
QObject::connect(google, SIGNAL(granted()), this, SLOT(testSlot()));
google->grant(); //start authorization process
}
void
googleDriveInterface::uploadFile(const QString &filePath) {
QUrl uploadURL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
QNetworkRequest request(uploadURL);
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "unable to open: " << filePath << " for upload:" << file.errorString();
return;
}
//get size of file and save as qbytearray for request header
QByteArray fileSize;
fileSize.setNum(file.size());
//get MIME type of file
QMimeDatabase mimeDB; //contains a database of all MIME types
QMimeType mime = mimeDB.mimeTypeForFile(filePath);
QByteArray mimeType = mime.name().toUtf8();
QByteArray test = authToken.toUtf8();
//set headers
request.setRawHeader("Authorization", authToken.toUtf8()); //convert authToken to QByteArray when we set header;
request.setRawHeader("Content-Type", "application/json; charset=UTF-8");
request.setRawHeader("Content-Length", fileSize);
request.setRawHeader("X-Upload-Content-Type", mimeType);
QByteArray fileData = file.readAll();
file.close();
networkReply = networkManager->post(request, fileData);
QObject::connect(networkReply, SIGNAL(metaDataChanged()), this, SLOT(testSlot1()));
}
I am expecting to get 200 Status Ok but as previously mentioned I am getting error 401 unauthorized.
error 401 unauthorized
Basically means that the request you are making has not been authorized.
I am not a C++ dev this is just a guess from my oauth2 knowlage. The access token must be a bearer token as far as i can see your not passing bearer.
request.setRawHeader("Authorization", "bearer " authToken.toUtf8());
I try to implement a REST client in order to get a Service Ticket from my server. For those of you who don't know CAS: A Service Ticket can be requested by showing a TGT. The TGT can be requested by a successful login basically. Maybe that is not even relevant.
I quess I have a error in my connect. My server is not even reacting to that connection and the reply is emptry. However for some reason reply->error() == QNetworkReply::NoErroris true.
What am I doing wrong?
bool Client::validateTGT(QString const & tgt) const
{
bool isValid = false;
QUrl url = QUrl("https://localhost:8943/cas/v1/tickets/" + tgt);
QUrl postData;
postData.addQueryItem("service", "https://test.de");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
//QNetworkAccessManager *networkManager = new QNetworkAccessManager();
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)), Qt::AutoConnection);
QNetworkReply *reply = manager->post(request, postData.encodedQuery());
QByteArray replyData = reply->readAll();
QString s_data = QString::fromAscii(replyData.data());
if (reply->error() == QNetworkReply::NoError)
{
isValid = true;
}
return isValid;
}
EDIT: replyFinished as requested
.h:
public slots:
void replyFinished(QNetworkReply *);
.cpp:
void CCASRESTClient::replyFinished(QNetworkReply *reply)
{
QByteArray replyData = reply->readAll();
serviceTicket = QString::fromAscii(replyData.data());
}
The slot replyFinished is called by the event loop when the reply has arrived from the server, this happens far after your function validateTGT has returned.
The manager object receives the reply and then emits the finished signal, this is when the slot replyFinished is called. It doesn't make sense to return a value from there. Just ask yourself, who is the caller function that will get this return value? validateTGT has already returned, thus it is not getting anything.
Your slot needs to be declared to return void, and you should do whatever you want to do with the reply in your replyFinished slot.
In general, if you have any slot that returns a value, the return value cannot be retrieved unless this slot was called as a normal function (Obviously this is not the case here).
i'm trying to load some data from a server and fill a Qt list. i want to run the dowloanding in a thread. so there is the code of:
principal function in the App.cpp
loadInterestPlaces(QString& urlInterestPlaces) {
LoadData* Data = new LoadData(urlInterestPlaces);
QFuture< list <InterestPlace *> > future = QtConcurrent::run(Data,
&LoadData::startLoading);
// Invoke our onLoadingFinished slot after the loading has finished.
bool ok = connect(&m_watcher, SIGNAL(finished()), this,
SLOT(onLoadingFinished()));
Q_ASSERT(ok);
Q_UNUSED(ok);
// starts watching the given future
m_watcher.setFuture(future);
}
void ApplicationUI::onLoadingFinished() {
qDebug() << "Loading finished";
interestPlacesList = m_watcher.future().result();
qDebug() << "List size= " << interestPlacesList.size();
}
}
the LoadData.cpp file : this is the code of the startloanding function :
std::list<InterestPlace *> LoadData::startLoading()
{
QNetworkAccessManager* netManager = new QNetworkAccessManager(this);
const QUrl url(_URL);
QNetworkRequest request(url);
QNetworkReply* reply = netManager->get(request);
netManager->moveToThread(this->thread());
netManager->setParent(this);
bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
qDebug() << reply->isFinished();
Q_ASSERT(ok);
Q_UNUSED(ok);
qDebug() << "load data: liste size" <<interestPlacesList.size();
return interestPlacesList;
}
Finally inside the SLOT onreplyfinished i parse the data and fill the list.
But the problem here, the QFuture is finished before the downloading so that the list is always empty.
How could i return the list filled just after the execution of the onReplyFinished ?
You may be making this more complex than you need to. Here is what I do to get data off the web. In my case I'm downloading a large ZIP file, writing it out and then unziping it, but the basic steps are the same (I have omitted a lot of my specific code for clarity):
void HtmlDownloader::startDownload() {
// Creates the network request and sets the destination URL
QNetworkRequest request = QNetworkRequest();
request.setUrl(mUrl);
// Creates the network access manager and connects a custom slot to its
// finished signal. Checks the return value for errors.
QNetworkAccessManager *networkAccessManager =
new QNetworkAccessManager(this);
bool c = connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(requestFinished(QNetworkReply*)));
Q_ASSERT(c);
// Indicate that the variable res isn't used in the rest of the app, to prevent
// a compiler warning
Q_UNUSED(c);
// Sends the request
QNetworkReply *reply = networkAccessManager->get(request);
c = connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(onDownloadProgress(qint64,qint64)));
Q_ASSERT(c);
}
void HtmlDownloader::requestFinished(QNetworkReply *reply) {
// Handle the reply data...
// Check the network reply for errors
if (reply->error() == QNetworkReply::NoError) {
// use reply->readAll() or reply->read() to get the returned data
// and process into a list. The list can't be returned but could
// be sent as a payload of a signal, or stored in a data member and
// a signal sent to indicate it is available.
}
reply->deleteLater();
}
void HtmlDownloader::onDownloadProgress(qint64 bytesRecieved, qint64 bytesTotal) {
emit downloadProgress(bytesRecieved, bytesTotal);
}