Problems with Adding Oauth2 Implicit Flow Class to NetworkAuth Module
-
Hello everybody,
i want to use the Networkauth API for the Implicit Flow of Oauth2. Since there is only this Authorization Flow class, i wrote a similar class to support the Implicit Flow. I followed this Guide on how to add a new module. Since the two flows only differ slightly, i just copied most of the Authorization-Class, renamed it, removed the unnecessary parts and added all the files accordingly. I can load up the
qtnetworkauth
module and i can compile it with no errors. The output tells me that it compiles my new classQOAuth2ImplicitFlow
and everything looks fine. But when i try to run a test that i wrote inqtnetworkauth/tests/auto/oauth2/tst_oauth2.cpp
its gives me this error:Qt/5.15.1/Src/qtnetworkauth/tests/auto/oauth2/tst_oauth2: undefined symbol: _ZN19QOAuth2ImplicitFlowC1EP7QObject, version Qt_5
When i try to use my class in the Reddit Example, it gives me some linker erorrs:
g++ -Wl,-O1 -Wl,-rpath,/home/mdeg/Qt/5.15.1/gcc_64/lib -o redditclient main.o redditmodel.o redditwrapper.o moc_redditmodel.o moc_redditwrapper.o /home/mdeg/Qt/5.15.1/gcc_64/lib/libQt5Widgets.so /home/mdeg/Qt/5.15.1/gcc_64/lib/libQt5Gui.so /home/mdeg/Qt/5.15.1/gcc_64/lib/libQt5NetworkAuth.so /home/mdeg/Qt/5.15.1/gcc_64/lib/libQt5Network.so /home/mdeg/Qt/5.15.1/gcc_64/lib/libQt5Core.so -lGL -lpthread /usr/bin/ld: main.o: in function `RedditModel::~RedditModel()': main.cpp:(.text._ZN11RedditModelD2Ev[_ZN11RedditModelD5Ev]+0x6b): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' /usr/bin/ld: main.o: in function `RedditModel::~RedditModel()': main.cpp:(.text._ZN11RedditModelD0Ev[_ZN11RedditModelD5Ev]+0x6b): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' /usr/bin/ld: redditmodel.o: in function `RedditModel::RedditModel(QString const&, QObject*) [clone .cold]': redditmodel.cpp:(.text.unlikely+0x139): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' /usr/bin/ld: redditwrapper.o: in function `RedditWrapper::RedditWrapper(QObject*)': redditwrapper.cpp:(.text+0x76b): undefined reference to `QOAuth2ImplicitFlow::QOAuth2ImplicitFlow(QObject*)' /usr/bin/ld: redditwrapper.o: in function `RedditWrapper::grant()': redditwrapper.cpp:(.text+0x4589): undefined reference to `QOAuth2ImplicitFlow::grant()' /usr/bin/ld: redditwrapper.o: in function `RedditWrapper::RedditWrapper(QObject*) [clone .cold]': redditwrapper.cpp:(.text.unlikely+0x151): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' /usr/bin/ld: redditwrapper.o: in function `RedditWrapper::RedditWrapper(QString const&, QObject*) [clone .cold]': redditwrapper.cpp:(.text.unlikely+0x1b7): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' /usr/bin/ld: moc_redditwrapper.o: in function `RedditWrapper::~RedditWrapper()': moc_redditwrapper.cpp:(.text._ZN13RedditWrapperD2Ev[_ZN13RedditWrapperD5Ev]+0x1c): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' /usr/bin/ld: moc_redditwrapper.o: in function `RedditWrapper::~RedditWrapper()': moc_redditwrapper.cpp:(.text._ZN13RedditWrapperD0Ev[_ZN13RedditWrapperD5Ev]+0x1c): undefined reference to `QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow()' collect2: error: ld returned 1 exit status make: *** [Makefile:288: redditclient] Error 1 16:44:21: The process "/usr/bin/make" exited with code 2. Error while building/deploying project redditclient (kit: Desktop Qt 5.15.1 GCC 64bit) When executing step "Make" 16:44:21: Elapsed time: 00:01.
I know its a linking error but i cant manage to fix it or find the cause for it. Am i missing something? The .pro file looks like this and should be good i guess:
QT += widgets network networkauth requires(qtConfig(listview)) TARGET = redditclient # Input SOURCES += main.cpp \ redditmodel.cpp \ redditwrapper.cpp HEADERS += \ redditmodel.h \ redditwrapper.h # install target.path = $$[QT_INSTALL_EXAMPLES]/oauth/redditclient INSTALLS += target
I did not post my code since it is pretty big but i can upload it somewhere if it helps.
Thanks in advance!
-
Hi and welcome to devnet,
Can you share your class declaration and code ?
-
Thanks for the quick answer! I removed the licence comments at the top of each file so they are not that big here.
qoauth2implicitflow.h
#ifndef QOAUTH2IMPLICITFLOW_H #define QOAUTH2IMPLICITFLOW_H #include <QtNetworkAuth/qoauthglobal.h> #include <QtNetworkAuth/qabstractoauth2.h> QT_BEGIN_NAMESPACE class QUrl; class QString; class QNetworkAccessManager; class QOAuth2ImplicitFlowPrivate; class Q_OAUTH_EXPORT QOAuth2ImplicitFlow : public QAbstractOAuth2 { Q_OBJECT public: explicit QOAuth2ImplicitFlow(QObject *parent = nullptr); explicit QOAuth2ImplicitFlow(QNetworkAccessManager *manager, QObject *parent = nullptr); QOAuth2ImplicitFlow(const QString &clientIdentifier, QNetworkAccessManager *manager, QObject *parent = nullptr); QOAuth2ImplicitFlow(const QUrl &authorizationUrl, QNetworkAccessManager *manager, QObject *parent = nullptr); QOAuth2ImplicitFlow(const QString &clientIdentifier, const QUrl &authorizationUrl, QNetworkAccessManager *manager, QObject *parent = nullptr); ~QOAuth2ImplicitFlow(); public Q_SLOTS: void grant() override; protected: QUrl buildAuthenticateUrl(const QVariantMap ¶meters = QVariantMap()); void resourceOwnerAuthorization(const QUrl &url, const QVariantMap ¶meters = QVariantMap()) override; private: Q_DISABLE_COPY(QOAuth2ImplicitFlow) Q_DECLARE_PRIVATE(QOAuth2ImplicitFlow) }; #endif // QOAUTH2IMPLICITFLOW_H
qoauth2implicitflow_p.h
#ifndef QOAUTH2IMPLICITFLOW_P_H #define QOAUTH2IMPLICITFLOW_P_H #ifndef QT_NO_HTTP #include <private/qabstractoauth2_p.h> #include <QtNetworkAuth/qoauthglobal.h> #include <QtNetworkAuth/qoauth2implicitflow.h> #include <QtCore/qstring.h> #include <QtCore/qdatetime.h> QT_BEGIN_NAMESPACE class QOAuth2ImplicitFlowPrivate : public QAbstractOAuth2Private { Q_DECLARE_PUBLIC(QOAuth2ImplicitFlow) public: QOAuth2ImplicitFlowPrivate(const QUrl &authorizationUrl, const QString &clientIdentifier, QNetworkAccessManager *manager = nullptr); void _q_handleCallback(const QVariantMap &data); void _q_authenticate(QNetworkReply *reply, QAuthenticator *authenticator); QString tokenType; QPointer<QNetworkReply> currentReply; }; QT_END_NAMESPACE #endif // QT_NO_HTTP #endif // QOAUTH2IMPLICITFLOW_P_H
qoauth2implicitflow.cpp
#ifndef QT_NO_HTTP #include <qoauth2implicitflow.h> #include <private/qoauth2implicitflow_p.h> #include <qmap.h> #include <qurl.h> #include <qvariant.h> #include <qurlquery.h> #include <qjsonobject.h> #include <qjsondocument.h> #include <qauthenticator.h> #include <qoauthhttpserverreplyhandler.h> #include <functional> QT_BEGIN_NAMESPACE /*! \class QOAuth2ImplicitFlow \inmodule QtNetworkAuth \ingroup oauth \brief The QOAuth2ImplicitFlow class provides an implementation of the \l {https://tools.ietf.org/html/rfc6749#section-4.2} {Implicit Grant} flow. \since 5.8 This class implements the \l {https://tools.ietf.org/html/rfc6749#section-4.2} {Implicit Grant} flow, which is used both to obtain access tokens. It is a redirection-based flow so the user will need access to a web browser. */ QOAuth2ImplicitFlowPrivate::QOAuth2ImplicitFlowPrivate( const QUrl &authorizationUrl, const QString &clientIdentifier, QNetworkAccessManager *manager) : QAbstractOAuth2Private(qMakePair(clientIdentifier, QString()), authorizationUrl, manager) { responseType = QStringLiteral("token"); } void QOAuth2ImplicitFlowPrivate::_q_handleCallback(const QVariantMap &data) { Q_Q(QOAuth2ImplicitFlow); 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); } void QOAuth2ImplicitFlowPrivate::_q_accessTokenRequestFinished(const QVariantMap &values) { Q_Q(QOAuth2ImplicitFlow); using Key = QAbstractOAuth2Private::OAuth2KeyString; if (values.contains(Key::error)) { const QString error = values.value(Key::error).toString(); qCWarning(loggingCategory, "Error: %s", qPrintable(error)); return; } bool ok; const QString accessToken = values.value(Key::accessToken).toString(); tokenType = values.value(Key::tokenType).toString(); int expiresIn = values.value(Key::expiresIn).toInt(&ok); if (!ok) expiresIn = -1; scope = values.value(Key::scope).toString(); if (accessToken.isEmpty()) { qCWarning(loggingCategory, "Access token not received"); return; } q->setToken(accessToken); const QDateTime currentDateTime = QDateTime::currentDateTime(); if (expiresIn > 0 && currentDateTime.secsTo(expiresAt) != expiresIn) { expiresAt = currentDateTime.addSecs(expiresIn); Q_EMIT q->expirationAtChanged(expiresAt); } setStatus(QAbstractOAuth::Status::Granted); } void QOAuth2ImplicitFlowPrivate::_q_authenticate(QNetworkReply *reply, QAuthenticator *authenticator) { if (reply == currentReply){ const auto url = reply->url(); // if (url == accessTokenUrl) { qDebug() << "accessToken Artifact \n"; authenticator->setUser(clientIdentifier); authenticator->setPassword(QString()); // } } } /*! Constructs a QOAuth2ImplicitFlow object with parent object \a parent. */ QOAuth2ImplicitFlow::QOAuth2ImplicitFlow(QObject *parent) : QOAuth2ImplicitFlow(nullptr, parent) {} /*! Constructs a QOAuth2ImplicitFlow object using \a parent as parent and sets \a manager as the network access manager. */ QOAuth2ImplicitFlow::QOAuth2ImplicitFlow(QNetworkAccessManager *manager, QObject *parent) : QOAuth2ImplicitFlow(QString(), manager, parent) {} /*! Constructs a QOAuth2ImplicitFlow object using \a parent as parent and sets \a manager as the network access manager. The client identifier is set to \a clientIdentifier. */ QOAuth2ImplicitFlow::QOAuth2ImplicitFlow(const QString &clientIdentifier, QNetworkAccessManager *manager, QObject *parent) : QAbstractOAuth2(*new QOAuth2ImplicitFlowPrivate(QUrl(), clientIdentifier, manager), parent) {} /*! Constructs a QOAuth2ImplicitFlow object using \a parent as parent and sets \a manager as the network access manager. The authenticate URL is set to \a authenticateUrl and the access token URL is set to \a accessTokenUrl. */ QOAuth2ImplicitFlow::QOAuth2ImplicitFlow(const QUrl &authenticateUrl, QNetworkAccessManager *manager, QObject *parent) : QAbstractOAuth2(*new QOAuth2ImplicitFlowPrivate(authenticateUrl, QString(), manager), parent) {} /*! Constructs a QOAuth2ImplicitFlow object using \a parent as parent and sets \a manager as the network access manager. The client identifier is set to \a clientIdentifier the authenticate URL is set to \a authenticateUrl and the access token URL is set to \a accessTokenUrl. */ QOAuth2ImplicitFlow::QOAuth2ImplicitFlow(const QString &clientIdentifier, const QUrl &authenticateUrl, QNetworkAccessManager *manager, QObject *parent) : QAbstractOAuth2(*new QOAuth2ImplicitFlowPrivate(authenticateUrl, clientIdentifier, manager), parent) {} /*! Destroys the QOAuth2ImplicitFlow instance. */ QOAuth2ImplicitFlow::~QOAuth2ImplicitFlow() {} /*! Starts the authentication flow as described in \l {https://tools.ietf.org/html/rfc6749#section-4.2}{The OAuth 2.0 Authorization Framework} */ void QOAuth2ImplicitFlow::grant() { Q_D(QOAuth2ImplicitFlow); if (d->authorizationUrl.isEmpty()) { qCWarning(d->loggingCategory, "No authenticate Url set"); return; } resourceOwnerAuthorization(d->authorizationUrl); } /*! Generates an authentication URL to be used in the \l {https://tools.ietf.org/html/rfc6749#section-4.2.1} {Authorization Request} using \a parameters. */ QUrl QOAuth2ImplicitFlow::buildAuthenticateUrl(const QVariantMap ¶meters) { Q_D(QOAuth2ImplicitFlow); using Key = QAbstractOAuth2Private::OAuth2KeyString; if (d->state.isEmpty()) setState(QAbstractOAuth2Private::generateRandomState()); Q_ASSERT(!d->state.isEmpty()); const QString state = d->state; QVariantMap p(parameters); QUrl url(d->authorizationUrl); p.insert(Key::responseType, responseType()); p.insert(Key::clientIdentifier, d->clientIdentifier); p.insert(Key::redirectUri, callback()); p.insert(Key::scope, d->scope); p.insert(Key::state, state); if (d->modifyParametersFunction) d->modifyParametersFunction(Stage::RequestingAuthorization, &p); url.setQuery(d->createQuery(p)); connect(d->replyHandler.data(), &QAbstractOAuthReplyHandler::callbackReceived, this, &QOAuth2ImplicitFlow::authorizationCallbackReceived, Qt::UniqueConnection); setStatus(QAbstractOAuth::Status::NotAuthenticated); qCDebug(d->loggingCategory, "Generated URL: %s", qPrintable(url.toString())); return url; } /*! Builds an authentication URL using \a url and \a parameters. This function emits an authorizeWithBrowser() signal to require user interaction. */ void QOAuth2ImplicitFlow::resourceOwnerAuthorization(const QUrl &url, const QVariantMap ¶meters) { Q_D(QOAuth2ImplicitFlow); if (Q_UNLIKELY(url != d->authorizationUrl)) { qCWarning(d->loggingCategory, "Invalid URL: %s", qPrintable(url.toString())); return; } const QUrl u = buildAuthenticateUrl(parameters); QObjectPrivate::connect(this, &QOAuth2ImplicitFlow::authorizationCallbackReceived, d, &QOAuth2ImplicitFlowPrivate::_q_handleCallback, Qt::UniqueConnection); Q_EMIT authorizeWithBrowser(u); } QT_END_NAMESPACE #endif // QT_NO_HTTP
-
Maybe a silly question but did you rebuild and install the module ?
If so, did you add these files to the relevant .pro file ? -
Well i recompiled it and added all files to the relevant .pro files. The compiling output created the .o files and the compiling process looked fine. Do i have to do extra steps to install this? Qt creator finds the
#include <QOAuth2ImplicitFlow>
module and recognizes the functions. Just the linker does not. -
But did you install the module after rebuilding it ?
-
Thats it! Forgot the
make install
:( Well thanks for your help! It works now. The test runs and the linker errors are gone. Once it works as intendet (the implicit flow) I might do a pull request since I want to use it in the OpenAPI Code-Generator for Qt5. Thanks again! -
Great !
The patch submission is a really good idea. Do not hesitate :-)