I am using QOAuth2AuthorizationCodeFlow to perform OIDC authentication.
I can connect to the signal QAbstractOAuth::granted() and be notified when it worked with success. Fine.
My problem is: how to be notified when something wrong happened?
I tried to connect to the QAbstractOAuth2::error() signal but I am not notified.
I used:
QObject::connect(this, SIGNAL(error(const QString &, const QString &, const QUrl &)), this, SLOT(catchAll()));
In the application console I see the error reported by the server:
qt.networkauth.replyhandler: Error transferring https://idp.safenetid.com/auth/realms/2H31DFOIEQ-STA/protocol/openid-connect/token - server replied: Fake Bad request
So Qt detected the problem. It looks like the probem is detected in QHttpThreadDelegate::finishedSlot():
if (httpReply->statusCode() >= 400) {
// it's an error reply
QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
"Error transferring %1 - server replied: %2"));
msg = msg.arg(httpRequest.url().toString(), httpReply->reasonPhrase());
emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
}
An error() signal is emitted. But it is catched by Qt itself and not reported upstream to the application?
Note:
Sometimes the server I use replies with an HTTP error code 400 and message "User not found".
Here I faked the problem using mitmproxy to be able to reproduce the issue easily.
PS: I think it is a missing feature in Qt. So I created an issue for QOAuth2AuthorizationCodeFlow::requestAccessToken() at https://bugreports.qt.io/browse/QTBUG-102279
You can get notified by implementing your own OAuthHttpServerReplyHandler.
This handler has to be set to your QOAuth2AuthorizationCodeFlow by using its setReplyHandler(_).
The OAuthHttpServerReplyHandler has to override the void networkReplyFinished(QNetworkReply *reply) which will get called by QT.
tl;dr: Connect to QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived,
and write your own handler.
Looking at the source code the problem seems to be that only one of these case statements will actually emit the error signal. They all return without a Q_EMIT error(...) call.
void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &data)
{
Q_Q(QOAuth2AuthorizationCodeFlow);
using Key = QAbstractOAuth2Private::OAuth2KeyString;
if (status != QAbstractOAuth::Status::NotAuthenticated) {
qCWarning(loggingCategory, "Unexpected call");
return;
}
Q_ASSERT(!state.isEmpty());
const QString error = data.value(Key::error).toString();
const QString code = data.value(Key::code).toString();
const QString receivedState = data.value(Key::state).toString();
if (error.size()) {
const QString uri = data.value(Key::errorUri).toString();
const QString description = data.value(Key::errorDescription).toString();
qCWarning(loggingCategory, "AuthenticationError: %s(%s): %s",
qPrintable(error), qPrintable(uri), qPrintable(description));
Q_EMIT q->error(error, description, uri);
return;
}
if (code.isEmpty()) {
qCWarning(loggingCategory, "AuthenticationError: Code not received");
return;
}
if (receivedState.isEmpty()) {
qCWarning(loggingCategory, "State not received");
return;
}
if (state != receivedState) {
qCWarning(loggingCategory, "State mismatch");
return;
}
setStatus(QAbstractOAuth::Status::TemporaryCredentialsReceived);
QVariantMap copy(data);
copy.remove(Key::code);
extraTokens = copy;
q->requestAccessToken(code);
}
But there also seems to be a misunderstanding as to what the error signal is for; Based on the parameters in its declaration,
void QAbstractOAuth2::error(const QString &error, const QString &errorDescription, const QUrl &uri)
The error signal is specifically for the HTTP 400 "Bad Request" response as described in rfc6749, rather than as a signal for all errors, which is why it won't emit signal on (client-side) errors for things like state mismatch (which could be a quite serious error).
If you are still keen on propagating the error signal, however, you can connect to the &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived signal as the private class does, then emit the error signal. It takes a bit of creativity because members in the code flow private class are not available, but it can be done.
For example, if you want to propagate error on a state mismatch, that might look like this:
{
static QString last_state;
QObject::connect(
&oauth2, &QOAuth2AuthorizationCodeFlow::stateChanged,
[&](const QString& state) {
last_state = state;
});
QObject::connect(
&oauth2, QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived,
[&](const QVariantMap& data) {
const QString state = data.value("state").toString();
if(last_state != state) {
Q_EMIT oauth2.error(
"The state in request does not match the state in reply!",
"State Mismatch!",
QUrl());
}
});
}
Since this is an abuse of the error function, however, I would suggest putting all this in a different object or class; One that would have stateUpdated and onCallbackReceived slots and be capable of emitting a stateMismatch signal, maybe.
Related
I have started started working with Qt framework and after reading the documentation for Qt5 and some examples across some blogs, I wrote the following program but I does not seem to do the correct job.
I am writing a class for which I need to write a method Login and logout.
For login method, I am writing following code:
void User::login()
{
const QUrl loginUrl = (this->m_url).append("/api/auth/login");
QNetworkRequest loginRequest;
loginRequest.setUrl(loginUrl);
loginRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject body;
QJsonObject data;
data.insert("userName", this->m_userName);
data.insert("password", this->m_password);
body.insert("data", data);
body.insert("provider", "LDAP");
//loginRequest.setBody(QJsonDocument(body).toJson());
const QByteArray json = QJsonDocument(body).toJson();
QNetworkReply* reply = m_manager.post(loginRequest, QJsonDocument(body).toJson());
while (!reply->isFinished())
{
// wait for the request to complete
}
QByteArray response_data = reply->readAll();
QJsonDocument responseJson = QJsonDocument::fromJson(response_data);
reply->deleteLater();
std::cout << response_data.toStdString() << std::endl;
}
After I call this method in my main function, If I check in fiddler, I cannot see any request made also, the program goes into infinite loop. Can you tell me what is wrong?
Cause
As you say, the program goes into infinite loop. The very one you have created:
while (!reply->isFinished())
{
// wait for the request to complete
}
Simply put, with QNetworkReply* reply = m_manager.post(loginRequest, QJsonDocument(body).toJson()); you do not send a request, but merely declare your wish to do so. Right after that you block the event loop, leaving Qt no chance to fulfill your wish.
Solution
Use signals and slots as intended. Think about team as events and callbacks. A good starting point is the description of QNetworkAccessManager:
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished,
this, &MyClass::replyFinished);
I have an application which communicates with an external application and this controls a devices. When new data have arrived in this device, my application should send an "answer" to this device. The communication with the external application is done with some callback functions and I use signals/slot to deliver the correct message to the callback.
My problem is that after the original signal returns, the application crashes with SIGSEGV error. As far as I know this error refers to failure in memory. As the external application could dealocate /destroy/corrupt the original data, could you please tell me if this a safe way to use signal/slot mechanism? Or this could have caused a SIGSEGV? Bellow, I am posting a code snippet as example:
MyClass::MyClass()
{
notifierData = new QWinEventNotifier(HandleData);
objectItem = new ObjectItem();
connect(notifierData,SIGNAL(activated(HANDLE)),SLOT(newDataSlot(HANDLE)));
QObject::connect(this, SIGNAL(objectData(int, QByteArray)), objectItem, SLOT(dataChangedSlot(int, QByteArray)), Qt::QueuedConnection) ;
}
void MyClass::newDataSlot(HANDLE)
{
DataType data;
GetDataFromExternalCallback(&data);
QByteArray bytearrayData(data.str);
emit objectData(data.TC_Index, bytearrayData);
}
void ObjectItem::dataChangedSlot(int index, QByteArray data)
{
emit dataChanged(index, data);
}
DeviceInterface::DeviceInterface()
{
Init(Line1);
}
void SetObjectItem(ObjectItem *anObjectItem)
{
objectItem = anObjectItem;
if(objectItem != NULL)
connect(objectItem, SIGNAL(dataChanged(int, QByteArray)), this, SLOT(newDataRecevied(int, QByteArray)), Qt::QueuedConnection);
}
void DeviceInterface::newDataRecevied(int index, QByteArray data)
{
QString messageToDisplay = "Reply Answer";
if(objectItem != NULL)
{
objectItem->updateDeviceDisplay(index, messageToDisplay);
}
}
bool DeviceInterface::updateDeviceDisplay(int index, QString text)
{
//Line1 is class member: char Line1[20]
sprintf_s(Line1, 20, "%s", UnicodeToCodePage(1253,text.toStdWString().c_str()));
SendDataWithExternalCallback(&Line1);
return true;
}
I need to get a json file from an url, fill it in a QtNetworkReply *reply and send reply in a connected fonction to convert it in QbyteArray to pars my Json response.
But when i go in my connected function, i cant fill QByteArray with that reply (always empty)
Here's my code :
int main(int ac, char *av[])
{
Borne borne(ac, av);
reply myReply;
QNetworkAccessManager networkManager;
QUrl url("http://vps202498.ovh.net:8080/ws/rest/v.1/stores/categories/150/products");
QNetworkRequest request;
request.setUrl(url);
myReply._reply = networkManager.get(request);
QObject::connect(myReply._reply, SIGNAL(finished()), &myReply, SLOT(fonction()));
myReply._reply->finished();
exit(1);
if (borne.initialize() == false)
return (false);
return (borne._app->exec());
}
And here's my connected function :
IProduct *reply::fonction()
{
QByteArray List;
std::cout << "connected" << std::endl;
List = _reply->readAll();
if (List.isNull())
exit(6);
return (NULL);
}
My .H :
class reply : public QObject
{
Q_OBJECT
public:
reply() {};
~reply() {};
QNetworkReply *_reply;
public slots:
IProduct *fonction();
private :
};
I cant std::cout "connected", but always quit with error log '6'.
I dont really know where am i doing mistake (Iam used to C, not Cpp), i've read all the man of Qt about it, and cant figure what going wrong.
Any ideas?
Thank you and apologize for weak skill and english
You call the finish() function manually immediately after creation of the request. In that moment the request is not even started, so there is nothing to read from _reply->readAll(). The reply finished signal should be called by the even loop after calling application exec().
Remove lines:
myReply._reply->finished();
exit(1);
The request will be processed asyncronously in the event loop.
Other issues:
the slot reply::fonction() does not need any retrun value;
the event loop may be not started because of (borne.initialize() == false).
Under Qt 4.7.1, OS X 10.6.8
(have to use this -- later versions
of Qt and/or OS X introduce severe
incompatibilities for my users)
The following works. Sometimes. Then sometimes not.
When it doesn't work, it returns "Unknown Error"
hst is good in all cases, qDebug returns same correct
data for hst every time.
The idea is, use ->get to pull a CGI URL; the CGI
returns some data, which I can ignore in this case.
Then I'm done.
hst is a well formed URL,
http://yadda.com/cgi-bin/whatever.py
QString hst;
QNetworkReply *qnr;
QNetworkAccessManager *qqnap = NULL;
qqnap = new(std::nothrow) QNetworkAccessManager(tmw);
if (qqnap != NULL)
{
hst = loaduphst(); // get qstring to send
qnr = qqnap->get(QNetworkRequest(QUrl(hst))); // report in and fetch update info
if (qnr->waitForReadyRead(3000) == FALSE)
{
qDebug() << "waitForReadyRead() returned FALSE -- error or timeout:" << qnr->errorString();
}
}
else
{
qDebug() << "qqnap is NULL";
}
yadda.com is up; the target script is dead simple
and works fine from browser or cmd line every time.
This is running within the context of
MainWindow::closeEvent(QCloseEvent *ce)
before I emit ce->accept() GUI is still up,
etc.
Hints? Tips? Abuse? Thanks!
waitForReadyRead is not implemented in QNetworkReply. The default implementation does nothing:
bool QIODevice::waitForReadyRead(int msecs)
{
Q_UNUSED(msecs);
return false;
}
Use the readyRead signal to find out when there is data available to be read.
More-or-less synchronous use of async networking is very problematic in the context of the main GUI loop. Signals that don't appear (finished OR readyRead), URLs that sometimes send and sometimes don't... and of course, as the kind person above pointed out, unimplemented functions. Zebras!
What we can do, though, is fire up an event loop and a timer on our own, and this will in a more-or-less friendly way act synchronous.
Perhaps some poor soul will need to poke a website CGI as I do; here's the code. It works. At least under Qt 4.7.1 it does!
So anyway, here it is:
QNetworkReply *qnr;
QNetworkAccessManager *qqnap;
QNetworkRequest qnwr;
QEventLoop w;
QTimer arf;
if ((qqnap = new(std::nothrow) QNetworkAccessManager(this)))
{
qnwr.setUrl(myUrl()); // Build web goodness
qnwr.setRawHeader("User-Agent", myUserAgent());
arf.setSingleShot(true);
if (connect(&arf, SIGNAL(timeout()), // timer firing blows...
&w, SLOT(quit()) // ...out event loop
) == FALSE)
{ return(BAD_CONNECT_TOUT); }
if (connect(qqnap, SIGNAL(finished(QNetworkReply*)), // notify we finished...
this, SLOT(qqnapReplyQ(QNetworkReply*)) // ...cuz I need to know
) == FALSE)
{ return(BAD_CONNECT_FINISHED_NOTIFY); }
if (connect(qqnap, SIGNAL(finished(QNetworkReply*)), // finishing blows out...
&w, SLOT(quit()) // ...event loop
) == FALSE)
{ return(BAD_CONNECT_FINISHED_ELOOP); }
if ((qnr = qqnap->get(qnwr))) // Go if qnr is good
{
arf.start(6000); // timeout in ms // Watchdog timer on
w.exec(); // handle all that
if (arf.isActive()) { arf.stop(); } // kill timer if needed
}
else { return(BAD_WWWGET); } // FAIL
}
else
{
return(BAD_NWAM); // FAIL
}
return(ZEN_NETWORKING);
I'm trying to validate a user's login, so I send a username and password to the server, the server checks that data against the database, and will send a yes/no if the validation was a success or failure. The client receives this and the readyRead() signal is emitted, and I handle that with a slot.
I have this login function:
bool Client::login(QString username, QString password){
//some code
client.write(clientSendBuf); //send the data to the server
//wait for response
//if response is good, return true
//else return false
}
I want to wait for a response from the server before I return a true or false with login. I know how to accept a response from the server just fine, but I basically want the data to be sent, and the client program to stop until either we get a response or some time has passed and we get a time out.
How do I do this in Qt?
http://qt-project.org/doc/qt-4.8/qiodevice.html#waitForReadyRead
QTcpSocket client;
if(client.waitForReadyRead(15000)){
//do something if signal is emitted
}
else{
//timeout
}
I didn't look through the docs properly. I found my answer.
You really do not want to write code like that. Remember that all waitFor... and exec methods can reenter your code and thus are a source of hard to find bugs. No, they will reenter at the most inopportune moment. Perhaps when you're demoing to a client, or perhaps when you've shipped your first system to Elbonia :)
The client should emit a signal when the login succeeds. There's a request to login, and a response to such a request. You can use the QStateMachine to direct the overall application's logic through such responses.
The example below presumes that the network protocol supports more than one request "on the wire" at any time. It'd be simple to get rid of the handler queue and allow just one handler.
class Client : public QObject {
Q_OBJECT
typedef bool (Client::*Handler)(); // returns true when the request is finished
QTcpSocket m_client;
QByteArray m_buffer;
QQueue<Handler> m_responses; // always has the idle response on the bottom
...
Q_SLOT void hasData() {
Q_ASSERT(! m_responses.isEmpty());
m_buffer += m_client.readAll();
while (! m_buffer.isEmpty()) {
if (m_reponses.head()()) m_responses.dequeue();
}
}
bool processIdleRsp() {
// Signal an error condition, we got data but expect none!
return false; // Must never return true, since this response mustn't be dequeued.
}
bool processLoginRsp() {
const int loginRspSize = ...;
if (m_buffer.size() < loginRspSize) return false;
bool success = false;
... // process the response
emit loginRsp(success);
m_buffer = m_buffer.mid(loginRspSize); // remove our response from the buffer
return true;
}
public:
Client(QObject * parent = 0) : QObject(parent), m_state(Idle) {
connect(&m_client, SIGNAL(readyRead()), SLOT(hasData());
m_responses.enqueue(&Client::processIdleRsp);
}
Q_SLOT void loginReq(const QString & username, const QString & password) {
QByteArray request;
QDataStream req(&request, QIODevice::WriteOnly);
...
m_client.write(request);
m_responses.enqueue(&Client::processLoginRsp);
}
Q_SIGNAL void loginRsp(bool success);
};
You could use a circular queue for the buffer to speed things up if you're transmitting lots of data. As-is, the remaining data is shoved to the front of the buffer after each response is processed.