/* Compile: ./c-test-qt-only # WCCOA_VERSION=321 qt-cc -etm321-netauth -g test-qt-only.cc -o test-qt-only -lNetworkAuth Execute: ./run-test-qt-only # WCCOA_VERSION=321 qt-run -etm321-netauth ./test-qt-only Work on desktop */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG qCritical().nospace().noquote() << "LOG " \ << QTime::currentTime().toString(QLatin1String("hh:mm:ss.zzz")) \ << " test-qt-only: " << __func__ << ":" << __LINE__ << ": " #define I qCritical().nospace().noquote() << "LOG pass " << __LINE__; #define myTr(expr) QString(QLatin1String(expr)) #define Resources__AuthClientId QLatin1String("winccoa") #define statusChanged(expr) LOG "STATUS " << expr QTextEdit *logTextEdit = 0; bool yError = false; /////////////////// void reportError(const QString &msg, ...) { if ( yError ) return; LOG "ERROR " << msg; //exit(-1); logTextEdit->setText(QString(QLatin1String("ERROR ")) + msg); yError = true; // qApp->quit(); } /////////////////// void wellDone(const QString &msg) { if ( yError ) return; LOG "WELL DONE Got token " << msg << " well done."; logTextEdit->setText(QString(QLatin1String("WELL DONE Got token %1 well done.")).arg(msg)); } //-------------------------------------------------------------------------------- QLatin1String callbackText( "
" "Login successful - please close this window
" ); //-------------------------------------------------------------------------------- class StOidcAuthUiHandler; StOidcAuthUiHandler *oidcHandler = nullptr; class StOidcAuthUiHandler { //class StOidcAuthUiHandler : public QObject { // Q_OBJECT //#define parentObj this #define parentObj qApp public: StOidcAuthUiHandler() { //manager = new QNetworkAccessManager(parentObj); manager = new QNetworkAccessManager(); //QObject::connect(manager, &QNetworkAccessManager::finished, this, &StOidcAuthUiHandler::networkReply); QObject::connect(manager, &QNetworkAccessManager::finished, [](QNetworkReply *reply) { oidcHandler->networkReply(reply); }); refreshLeadTime = 10; } //-------------------------------------------------------------------------------- void startAuth() { tryNextProvider(0); } //-------------------------------------------------------------------------------- void tryNextProvider(int idx) { providerIdx = idx < 0 ? ++providerIdx : idx; getOidcConfiguration(providerIdx); } //-------------------------------------------------------------------------------- void getOidcConfiguration(int idx) { authGranted = false; authWellKnownUrl.clear(); oneTimeAuthCode.clear(); tokenJson.clear(); LOG "idx:=" << idx; if (idx == 0) { authWellKnownUrl = QLatin1String("http://localhost:8080/realms/WINCCOA/.well-known/openid-configuration"); configUrl = QUrl(authWellKnownUrl); LOG "configUrl=" << configUrl; /*emit*/ statusChanged(myTr("Contacting identity provider #%1 at '%2'...") .arg(idx + 1) .arg(configUrl.host())); manager->get(QNetworkRequest(configUrl)); } else { // all providers tried reportError(myTr("OIDC authentication failed with all configured providers"), false); } } //-------------------------------------------------------------------------------- void networkReply(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { reportError(QLatin1String("%1 (%2)") .arg(reply->errorString()) .arg(reply->request().url().toString())); } else if (reply->request().url() == configUrl) { handleConfigReply(reply); } } //-------------------------------------------------------------------------------- void handleConfigReply(QNetworkReply *reply) { QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); if (!doc.isNull() && doc.isObject()) { QJsonObject obj = doc.object(); QString authUrl = obj.value(QLatin1String("authorization_endpoint")).toString(); QString tokenUrl = obj.value(QLatin1String("token_endpoint")).toString(); if (!authUrl.isEmpty() && !tokenUrl.isEmpty()) { if ( useSchemeAuthFlow ) { startSchemeAuthFlow(authUrl, tokenUrl); } else { startAuthFlow(authUrl, tokenUrl); } return; } } reportError(myTr("Invalid configuration received from %1").arg(reply->url().host())); } //-------------------------------------------------------------------------------- void startAuthFlow(const QString &authUrl, const QString &tokenUrl) { LOG "start"; // only one authorization flow instance can be used if (authFlow) return; // initialize authFlow with necessary details authFlow = new QOAuth2AuthorizationCodeFlow(Resources__AuthClientId, authUrl, tokenUrl, manager, parentObj); authFlow->setScope(QLatin1String("openid")); // // prepare timer for automatic refresh // refreshTimer = new QTimer(parentObj); // QObject::connect(refreshTimer, &QTimer::timeout, authFlow, &QOAuth2AuthorizationCodeFlow::refreshAccessToken); // TODO MA: if supported by OIDC Lite, PKCE should be activated authFlow->setPkceMethod(QOAuth2AuthorizationCodeFlow::PkceMethod::None); // make sure the auth page URL will be opened QObject::connect(authFlow, &QAbstractOAuth::authorizeWithBrowser, parentObj, &QDesktopServices::openUrl); // TODO MA: different reply handler for Android & iOS QOAuthHttpServerReplyHandler *handler = new QOAuthHttpServerReplyHandler(authFlow); // HTML body that tries to close the browser window // TODO MA: this currently only works if the user does not need to login // because of an active IdP session. handler->setCallbackText(callbackText); LOG "install handler"; // the one-time code is used by the plugin as a key, so store it when received in the first step QObject::connect(authFlow, &QAbstractOAuth2::authorizationCallbackReceived, parentObj, [this, handler] (const QVariantMap &data) { oneTimeAuthCode = data.value(QLatin1String("code")).toString(); handler->close(); }); // the plugin expects the complete reply with all tokens and additional information, // so store it here before authFlow gets the granted event QObject::connect(handler, &QAbstractOAuthReplyHandler::replyDataReceived, parentObj, [this] (const QByteArray &data) { tokenJson = QLatin1String(data); LOG "got tokenJson=" << tokenJson; }); // This is the final event, check that a token was received and accept the dialog to finish QObject::connect(authFlow, &QAbstractOAuth::granted, parentObj, [=] () { authGranted = !tokenJson.isEmpty(); LOG "QAbstractOAuth::granted: authGranted=" << authGranted; if (authGranted) { wellDone(tokenJson); //emit tokenReceived(); //startAutoRefresh(); } else { reportError(myTr("Internal error - no valid token received")); return; } }); // TODO MA: not needed on mobile platforms because using a different reply handler // look for a free port for the reply handler for (qint16 port = 30000; port < 50000; port++) { handler->close(); // handler must be closed before listening to next port if (handler->listen(QHostAddress::Any, port)) { LOG "listen on port " << port; break; // found free port } } authFlow->setReplyHandler(handler); // After everything is set up, finally initiate the flow if (handler->isListening()) { statusChanged(myTr("Please enter your credentials in the browser window that just opened")); authFlow->grant(); } else { reportError(myTr("Internal error - reply handler not listening")); } } //-------------------------------------------------------------------------------- void startSchemeAuthFlow(const QString &authUrl, const QString &tokenUrl) { LOG "start"; // only one authorization flow instance can be used if (authFlow) return; // initialize authFlow with necessary details authFlow = new QOAuth2AuthorizationCodeFlow(Resources__AuthClientId, authUrl, tokenUrl, manager, parentObj); authFlow->setScope(QLatin1String("openid")); // // prepare timer for automatic refresh // refreshTimer = new QTimer(this); // QObject::connect(refreshTimer, &QTimer::timeout, authFlow, &QOAuth2AuthorizationCodeFlow::refreshAccessToken); // TODO MA: if supported by OIDC Lite, PKCE should be activated authFlow->setPkceMethod(QOAuth2AuthorizationCodeFlow::PkceMethod::None); // make sure the auth page URL will be opened QObject::connect(authFlow, &QAbstractOAuth::authorizeWithBrowser, parentObj, &QDesktopServices::openUrl); // different reply handler for Android & iOS QOAuthUriSchemeReplyHandler *handler = new QOAuthUriSchemeReplyHandler(authFlow); //gl_handler = handler; LOG "install handler"; // the one-time code is used by the plugin as a key, so store it when received in the first step QObject::connect(authFlow, &QAbstractOAuth2::authorizationCallbackReceived, parentObj, [this, handler] (const QVariantMap &data) { oneTimeAuthCode = data.value(QLatin1String("code")).toString(); LOG "oneTimeAuthCode=" << oneTimeAuthCode; handler->close(); }); // the plugin expects the complete reply with all tokens and additional information, // so store it here before authFlow gets the granted event QObject::connect(handler, &QAbstractOAuthReplyHandler::replyDataReceived, parentObj, [this] (const QByteArray &data) { tokenJson = QLatin1String(data); LOG "got tokenJson=" << tokenJson; }); // This is the final event, check that a token was received and accept the dialog to finish QObject::connect(authFlow, &QAbstractOAuth::granted, parentObj, [=] () { LOG "QAbstractOAuth::granted"; authGranted = !tokenJson.isEmpty(); if (authGranted) { wellDone(tokenJson); // emit tokenReceived(); // startAutoRefresh(); } else { reportError(myTr("Internal error - no valid token received")); return; } }); handler->setRedirectUrl(QUrl(QLatin1String("winccoaui.siemens.com://oauth2redirect"))); authFlow->setReplyHandler(handler); LOG "handler->listen()"; // After everything is set up, finally initiate the flow if (handler->listen()) { statusChanged(myTr("Please enter your credentials in the browser window that just opened")); LOG "authFlow->grant(); pid=" << getpid(); authFlow->grant(); LOG "after authFlow->grant();"; } else { LOG "Internal error - reply handler not listening"; reportError(myTr("Internal error - reply handler not listening")); } } public: bool useSchemeAuthFlow = false; private: QNetworkAccessManager *manager = nullptr; QOAuth2AuthorizationCodeFlow *authFlow = nullptr; QTimer *refreshTimer = nullptr; QUrl configUrl; QString authWellKnownUrl; QString authUrl; QString oneTimeAuthCode; QString tokenUrl; QString tokenJson; bool authGranted = false; int providerIdx = 0; int refreshLeadTime = 0; }; // ///////// QHBoxLayout *buttonBar = 0; void addButton(const QString &label, std::function cb) { QPushButton *button = new QPushButton(label); QObject::connect(button, &QPushButton::clicked, cb); buttonBar->addWidget(button); } /////////////////// extern "C" int main(int argc, char **argv) { //printf("PID: %d\n", getpid()); //sleep(4); QApplication app(argc, argv); QMainWindow win; QVBoxLayout *mainVBox = new QVBoxLayout(); I oidcHandler = new StOidcAuthUiHandler(); buttonBar = new QHBoxLayout(); I addButton(myTr("Http Server"), []() { oidcHandler->useSchemeAuthFlow = false; I oidcHandler->startAuth(); }); addButton(myTr("Uri Scheme"), []() { oidcHandler->useSchemeAuthFlow = true; I oidcHandler->startAuth(); }); QWidget *buttonBarWidget = new QWidget(); buttonBarWidget->setLayout(buttonBar); mainVBox->addWidget(buttonBarWidget); logTextEdit = new QTextEdit(); logTextEdit->setReadOnly(true); logTextEdit->setFixedHeight(200); mainVBox->addWidget(logTextEdit); QWidget *mainVBoxWidget = new QWidget(); mainVBoxWidget->setLayout(mainVBox); win.setCentralWidget(mainVBoxWidget); win.show(); return app.exec(); } /////////////////// #define main main_v1