Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 class QOAuth2ImplicitFlow and everything looks fine. But when i try to run a test that i wrote in qtnetworkauth/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!


  • Lifetime Qt Champion

    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 &parameters = QVariantMap());
        void resourceOwnerAuthorization(const QUrl &url,
                                        const QVariantMap &parameters = 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 &parameters)
    {
        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 &parameters)
    {
        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
    
    

  • Lifetime Qt Champion

    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.


  • Lifetime Qt Champion

    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!


  • Lifetime Qt Champion

    Great !

    The patch submission is a really good idea. Do not hesitate :-)