From a62e9f2ea3bc4e5035005e9167231da3978b6e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 18 Mar 2024 08:25:18 +0100 Subject: [PATCH 01/30] Initial HttpAuth module. --- CMakeLists.txt | 5 ++- src/Utils/HttpAuth.cpp | 78 +++++++++++++++++++++++++++++++++++++++++ src/Utils/HttpAuth.h | 31 ++++++++++++++++ tests/test-HttpAuth.cpp | 19 ++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/Utils/HttpAuth.cpp create mode 100644 src/Utils/HttpAuth.h create mode 100644 tests/test-HttpAuth.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aa5955b..00f14c37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ set(CMAKE_AUTORCC ON) ############################################## # Find Qt dependencies ############################################## -set(MERKAARTOR_QT_LIBS Svg Network Xml Core Gui Concurrent PrintSupport Widgets Test) +set(MERKAARTOR_QT_LIBS Svg Network NetworkAuth Xml Core Gui Concurrent PrintSupport Widgets Test) set(MERKAARTOR_QT_TOOLS LinguistTools) find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) message(STATUS " * Qt version found: ${QT_VERSION}") @@ -257,6 +257,8 @@ src/Layers/LayerWidget.h src/Layers/LayerWidget.ui src/Layers/FilterEditDialog.ui src/Layers/LayerPrivate.h +src/Utils/HttpAuth.h +src/Utils/HttpAuth.cpp src/Utils/ShortcutOverrideFilter.h src/Utils/SelectionDialog.cpp src/Utils/ProjectionChooser.h @@ -686,6 +688,7 @@ endfunction() MERK_ADD_TEST(test-projection src/common/Projection.cpp src/common/Coord.cpp) MERK_ADD_TEST(test-OsmLink src/Utils/OsmLink.cpp src/common/Coord.cpp) +MERK_ADD_TEST(test-HttpAuth src/Utils/HttpAuth.cpp) # Additional tests that use the main executable if (EXTRA_TESTS) diff --git a/src/Utils/HttpAuth.cpp b/src/Utils/HttpAuth.cpp new file mode 100644 index 00000000..022419b5 --- /dev/null +++ b/src/Utils/HttpAuth.cpp @@ -0,0 +1,78 @@ + +#include "HttpAuth.h" +//#include +#include +#include + +//Client ID wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps +//Client Secret evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM +//Make sure to save this secret - it will not be accessible again +//Permissions +// +// Read user preferences (read_prefs) +// Modify user preferences (write_prefs) +// Create diary entries, comments and make friends (write_diary) +// Modify the map (write_api) +// Read private GPS traces (read_gpx) +// Upload GPS traces (write_gpx) +// Modify notes (write_notes) +// +//Redirect URIs +// +// http://127.0.0.1:1337/ + +QString codeVerifier("hello world"); +QString codeChallenge("hello world"); + +HttpAuth::HttpAuth(QObject *parent) : QObject(parent) { + auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); + oauth2.setReplyHandler(replyHandler); + oauth2.setAuthorizationUrl(QUrl("https://www.openstreetmap.org/oauth2/authorize")); + oauth2.setAccessTokenUrl(QUrl("https://www.openstreetmap.org/oauth2/token")); + oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); + oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); + + + qDebug() << "HttpAuth init."; + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=]( + QAbstractOAuth::Status status) { + qDebug() << "OAuth status changed to " << int(status); + if (status == QAbstractOAuth::Status::Granted) { + emit authenticated(); + auto reply = oauth2.get(QUrl("https://api.openstreetmap.org/api/0.6/user/details")); + connect(reply, &QNetworkReply::finished, [=]() { + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCritical() << "Reddit error:" << reply->errorString(); + return; + } + + qDebug() << reply->readAll(); + }); + } + }); + oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMap*params){ + qDebug() << "Stage: " << int(stage) << "with params" << params; + if (params) { + switch (stage) { + case QAbstractOAuth::Stage::RequestingAuthorization: + qDebug() << "Requesting Authorization."; + //params->insert("code_challenge", codeChallenge); + //params->insert("code_challenge_method", "plain"); // Implement S256 + break; + case QAbstractOAuth::Stage::RequestingAccessToken: + qDebug() << "Requesting Access Token."; + //params->insert("code_verifier", codeVerifier); + break; + default: + qDebug() << "default stage."; + } + } + + }); + + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, + &QDesktopServices::openUrl); +} + diff --git a/src/Utils/HttpAuth.h b/src/Utils/HttpAuth.h new file mode 100644 index 00000000..85de9c2a --- /dev/null +++ b/src/Utils/HttpAuth.h @@ -0,0 +1,31 @@ +#ifndef __HTTPAUTH_H__ +#define __HTTPAUTH_H__ + +#include +#include + +#include + +class HttpAuth : public QObject +{ + Q_OBJECT + +public: + HttpAuth(QObject *parent = nullptr); + + bool isPermanent() const {return false; } + void setPermanent(bool /*value*/) {}; + +public slots: + void grant() { oauth2.grant(); }; + +signals: + void authenticated(); + +private: + QOAuth2AuthorizationCodeFlow oauth2; + bool permanent = false; +}; + + +#endif diff --git a/tests/test-HttpAuth.cpp b/tests/test-HttpAuth.cpp new file mode 100644 index 00000000..20df44fa --- /dev/null +++ b/tests/test-HttpAuth.cpp @@ -0,0 +1,19 @@ +#include + +#include "Utils/HttpAuth.h" + +class TestHttpAuth : public QObject +{ + Q_OBJECT + private slots: + + + void simpleLogin() { + HttpAuth auth(this); + auth.grant(); + QTest::qWait(10000); + } +}; + +QTEST_MAIN(TestHttpAuth) +#include "test-HttpAuth.moc" From ee83abe7d5f6dc31bff63bade2db2277f44f92a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 18 Mar 2024 22:07:15 +0800 Subject: [PATCH 02/30] oauth2: Added PKCE with S256 support. --- CMakeLists.txt | 5 +++++ src/Utils/HttpAuth.cpp | 49 +++++++++++++++++++++++++++++++----------- src/Utils/HttpAuth.h | 3 +++ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 00f14c37..558d33c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -479,6 +479,7 @@ option(GPSD "Enable GPS Dock (requires gpsd library)." option(WEBENGINE "Enable the use of QtWeb engine (not supported on all platforms)" OFF) option(PROTOBUF "Enable support for .osm.pbf format." ON) option(EXTRA_TESTS "Enable extra tests that cannot be run automatically on CI build." ON ) +option(INTERACTIVE_TESTS "Enable interactive tests that need user input." OFF) message(STATUS "Build options (use -DOPT=ON/OFF to enable/disable):") message(STATUS " * ZBAR ${ZBAR}") @@ -688,7 +689,11 @@ endfunction() MERK_ADD_TEST(test-projection src/common/Projection.cpp src/common/Coord.cpp) MERK_ADD_TEST(test-OsmLink src/Utils/OsmLink.cpp src/common/Coord.cpp) + +# Additional tests that require user input +if (INTERACTIVE_TESTS) MERK_ADD_TEST(test-HttpAuth src/Utils/HttpAuth.cpp) +endif() # Additional tests that use the main executable if (EXTRA_TESTS) diff --git a/src/Utils/HttpAuth.cpp b/src/Utils/HttpAuth.cpp index 022419b5..d16958b5 100644 --- a/src/Utils/HttpAuth.cpp +++ b/src/Utils/HttpAuth.cpp @@ -3,9 +3,17 @@ //#include #include #include +#include +#include -//Client ID wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps -//Client Secret evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM + +//FIXME: The client ID and secret are not really a secret, since there is no +//way to hide them in a desktop application. In case of abuse, the client ID +//can be revoked by osm.org. It might be a good idea to make it configurable, +//so that the user can enter their own client ID and secret in this case. +// +//Client ID wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps +//Client Secret evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM // FIXME: The secret is not needed. Why? //Make sure to save this secret - it will not be accessible again //Permissions // @@ -21,8 +29,19 @@ // // http://127.0.0.1:1337/ -QString codeVerifier("hello world"); -QString codeChallenge("hello world"); +#include + +QString generateCodeVerifier() { + std::random_device rd; + std::mt19937 gen(rd()); + int bytes = 32; + std::uniform_int_distribution dis(0, 255); + QString result; + for (int i = 0; i < bytes; i++) { + result.append(QString::number(dis(gen), 16)); + } + return result; +} HttpAuth::HttpAuth(QObject *parent) : QObject(parent) { auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); @@ -32,47 +51,51 @@ HttpAuth::HttpAuth(QObject *parent) : QObject(parent) { oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); + codeVerifier = generateCodeVerifier(); + codeChallenge = QString(QCryptographicHash::hash(codeVerifier.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - qDebug() << "HttpAuth init."; connect(&oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=]( QAbstractOAuth::Status status) { qDebug() << "OAuth status changed to " << int(status); if (status == QAbstractOAuth::Status::Granted) { + qDebug() << "Granted, token: " << oauth2.token(); emit authenticated(); auto reply = oauth2.get(QUrl("https://api.openstreetmap.org/api/0.6/user/details")); connect(reply, &QNetworkReply::finished, [=]() { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - qCritical() << "Reddit error:" << reply->errorString(); + qCritical() << "Error:" << reply->errorString(); return; } qDebug() << reply->readAll(); }); + } else { + qWarning() << "Status is not granted: " << int(status); + emit failed(int(status)); } }); - oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMap*params){ - qDebug() << "Stage: " << int(stage) << "with params" << params; + oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap*params){ + qDebug() << "Stage: " << int(stage) << "with params" << *params; if (params) { switch (stage) { case QAbstractOAuth::Stage::RequestingAuthorization: qDebug() << "Requesting Authorization."; - //params->insert("code_challenge", codeChallenge); - //params->insert("code_challenge_method", "plain"); // Implement S256 + params->insert("code_challenge", codeChallenge); + params->insert("code_challenge_method", "S256"); break; case QAbstractOAuth::Stage::RequestingAccessToken: qDebug() << "Requesting Access Token."; - //params->insert("code_verifier", codeVerifier); + params->insert("code_verifier", codeVerifier); break; default: qDebug() << "default stage."; } } - + qDebug() << "Stage: " << int(stage) << "with params" << *params; }); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); } - diff --git a/src/Utils/HttpAuth.h b/src/Utils/HttpAuth.h index 85de9c2a..02cdf8bc 100644 --- a/src/Utils/HttpAuth.h +++ b/src/Utils/HttpAuth.h @@ -21,10 +21,13 @@ public slots: signals: void authenticated(); + void failed(int error); private: QOAuth2AuthorizationCodeFlow oauth2; bool permanent = false; + QString codeVerifier; + QString codeChallenge; }; From d3d3dd026108cc2785342db2f39a060d64b70b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sun, 24 Mar 2024 23:12:09 +0800 Subject: [PATCH 03/30] WIP: Mid refactoring. --- CMakeLists.txt | 1 + src/MainWindow.cpp | 1 + src/Preferences/MerkaartorPreferences.cpp | 5 +- src/Preferences/MerkaartorPreferences.h | 13 +- src/Preferences/OsmServerWidget.ui | 152 +++++++++++----- src/Preferences/PreferencesDialog.cpp | 56 +++++- src/Preferences/PreferencesDialog.h | 13 -- src/Utils/HttpAuth.cpp | 21 ++- src/Utils/HttpAuth.h | 6 +- src/Utils/OsmServer.cpp | 204 ++++++++++++++++++++++ src/Utils/OsmServer.h | 82 +++++++++ tests/test-HttpAuth.cpp | 4 + 12 files changed, 486 insertions(+), 72 deletions(-) create mode 100644 src/Utils/OsmServer.cpp create mode 100644 src/Utils/OsmServer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 558d33c2..6dda0d34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,7 @@ src/Utils/SlippyMapWidget.cpp src/Utils/SlippyMapWidget.h src/Utils/Utils.h src/Utils/OsmLink.cpp +src/Utils/OsmServer.cpp src/Utils/ShortcutOverrideFilter.cpp src/Utils/LineF.h src/Utils/SelectionDialog.ui diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f7830fa0..520f8de7 100755 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -2027,6 +2027,7 @@ void MainWindow::on_fileUploadAction_triggered() return; } on_editPropertiesAction_triggered(); + // TODO: Replace this call to use a the OsmServer object instead of individual parameters. syncOSM(M_PREFS->getOsmApiUrl(), M_PREFS->getOsmUser(), M_PREFS->getOsmPassword()); theDocument->history().updateActions(); diff --git a/src/Preferences/MerkaartorPreferences.cpp b/src/Preferences/MerkaartorPreferences.cpp index a2d79df3..4954c37b 100644 --- a/src/Preferences/MerkaartorPreferences.cpp +++ b/src/Preferences/MerkaartorPreferences.cpp @@ -990,6 +990,7 @@ QString MerkaartorPreferences::getOsmApiUrl() const if (u.path().isEmpty()) u.setPath("/api/" + apiVersion()); + qDebug() << "getOsmApiUrl" << u.toString(); return u.toString(); } @@ -1662,8 +1663,9 @@ void MerkaartorPreferences::loadOsmServers() int size = Sets->beginReadArray("OsmServers"); for (int i = 0; i < size; ++i) { Sets->setArrayIndex(i); - OsmServer server; + OsmServerInfo server; server.Selected = Sets->value("selected").toBool(); + server.Type = OsmServerInfo::typeFromString(Sets->value("type").toString()); server.Url = Sets->value("url").toString(); server.User = Sets->value("user").toString(); server.Password = Sets->value("password").toString(); @@ -1680,6 +1682,7 @@ void MerkaartorPreferences::saveOsmServers() for (int i = 0; i < theOsmServers.size(); ++i) { Sets->setArrayIndex(i); Sets->setValue("selected", theOsmServers.at(i).Selected); + Sets->setValue("type", OsmServerInfo::typeToString(theOsmServers.at(i).Type)); Sets->setValue("url", theOsmServers.at(i).Url); Sets->setValue("user", theOsmServers.at(i).User); Sets->setValue("password", theOsmServers.at(i).Password); diff --git a/src/Preferences/MerkaartorPreferences.h b/src/Preferences/MerkaartorPreferences.h index cb8fe64d..af4fc146 100644 --- a/src/Preferences/MerkaartorPreferences.h +++ b/src/Preferences/MerkaartorPreferences.h @@ -37,6 +37,8 @@ #include "IRenderer.h" +#include "OsmServer.h" + #include "build-metadata.hpp" class MainWindow; @@ -146,15 +148,8 @@ class Tool typedef QMap ToolList; typedef QMapIterator ToolListIterator; -struct OsmServer -{ - bool Selected; - QString Url; - QString User; - QString Password; -}; -typedef QList OsmServerList; -typedef QListIterator OsmServerIterator; +typedef QList OsmServerList; +typedef QListIterator OsmServerIterator; // Outside of merkaartorpreferences, because initializing it will need translations // Classic chicken & egg problem. diff --git a/src/Preferences/OsmServerWidget.ui b/src/Preferences/OsmServerWidget.ui index 4004cd1c..cb9cf96f 100644 --- a/src/Preferences/OsmServerWidget.ui +++ b/src/Preferences/OsmServerWidget.ui @@ -7,14 +7,23 @@ 0 0 552 - 69 + 195 Form - + + 0 + + + 0 + + + 0 + + 0 @@ -54,59 +63,120 @@ - - - - - Qt::Horizontal - - - - 57 - 20 - - - - + - + - User: + Authentication type - - - - - - Pwd: - - - - - - - QLineEdit::Password + + + 1 + + + Username and password (deprecated) + + + + + OAuth2 + + + + + + 1 + + + + + + + User: + + + + + + + + + + Pwd: + + + + + + + QLineEdit::Password + + + + + + + + + + + Login state: Unknown + + + + + + + Qt::Horizontal + + + + 309 + 20 + + + + + + + + Login + + + + + + + - rbOsmServerSelected - edOsmServerUrl - label_14 - edOsmServerUser - label_13 - edOsmServerPwd - tbOsmServerAdd - tbOsmServerDel - label_14 - + + + authType + currentIndexChanged(int) + credentials + setCurrentIndex(int) + + + 275 + 43 + + + 275 + 77 + + + + diff --git a/src/Preferences/PreferencesDialog.cpp b/src/Preferences/PreferencesDialog.cpp index 4e7d97c9..3b8c96c1 100644 --- a/src/Preferences/PreferencesDialog.cpp +++ b/src/Preferences/PreferencesDialog.cpp @@ -16,6 +16,7 @@ #include "Document.h" #include "Feature.h" #include "PropertiesDock.h" +#include "HttpAuth.h" #include #include @@ -36,11 +37,36 @@ static void makeBoundaryIcon(QToolButton* bt, QColor C) bt->setIcon(pm); } + +// TODO: Move the class to it's own file. +class OsmServerWidget : public QWidget, public Ui::OsmServerWidget +{ + Q_OBJECT + +public: + OsmServerWidget(QWidget * parent = 0, Qt::WindowFlags f = Qt::Widget); + +public slots: + void on_tbOsmServerAdd_clicked(); + void on_tbOsmServerDel_clicked(); + void on_rbOsmServerSelected_clicked(); + void on_tbOAuth2Login_clicked(); + void on_httpAuth_authenticated(); + void on_httpAuth_failed(); +private: + HttpAuth httpAuth; +}; + +#include "PreferencesDialog.moc" + OsmServerWidget::OsmServerWidget(QWidget * parent, Qt::WindowFlags f) : QWidget(parent, f) { setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + + connect(&httpAuth, &HttpAuth::authenticated, this, &OsmServerWidget::on_httpAuth_authenticated); + connect(&httpAuth, &HttpAuth::failed, this, &OsmServerWidget::on_httpAuth_failed); } void OsmServerWidget::on_tbOsmServerAdd_clicked() @@ -97,6 +123,29 @@ void OsmServerWidget::on_rbOsmServerSelected_clicked() rbOsmServerSelected->setChecked(true); } +void OsmServerWidget::on_tbOAuth2Login_clicked() { + qDebug() << "Start login..."; + httpAuth.setBaseUrl(QUrl(this->edOsmServerUrl->text()).resolved(QUrl("/"))); + httpAuth.Login(); + httpAuth.grant(); + this->lbLoginState->setText(tr("Authenticating")); + this->tbOAuth2Login->setEnabled(false); +} + +void OsmServerWidget::on_httpAuth_authenticated() { + qDebug() << "Authenticated!"; + this->edOsmServerUser->setText("oauth2"); + this->edOsmServerPwd->setText(httpAuth.token()); + this->lbLoginState->setText(tr("Authenticated")); + this->tbOAuth2Login->setEnabled(true); +} + +void OsmServerWidget::on_httpAuth_failed() { + qDebug() << "Failed!"; + this->lbLoginState->setText(tr("Failed")); + this->tbOAuth2Login->setEnabled(true); +} + PreferencesDialog::PreferencesDialog(QWidget* parent) : QDialog(parent) { @@ -189,6 +238,7 @@ void PreferencesDialog::loadPrefs() OsmServerWidget* wOSmServer = new OsmServerWidget(grpOSM); wOSmServer->edOsmServerUrl->setText(M_PREFS->getOsmApiUrl()); + // wOSmServer->authType->setCurrentIndex(0); // Keep default authType per UI. wOSmServer->edOsmServerUser->setText(M_PREFS->getOsmUser()); wOSmServer->edOsmServerPwd->setText(M_PREFS->getOsmPassword()); wOSmServer->rbOsmServerSelected->setChecked(true); @@ -196,10 +246,11 @@ void PreferencesDialog::loadPrefs() OsmServersLayout->addWidget(wOSmServer); } else { - foreach(OsmServer srv, *theOsmServers) { + foreach(OsmServerInfo srv, *theOsmServers) { OsmServerWidget* wOSmServer = new OsmServerWidget(grpOSM); wOSmServer->edOsmServerUrl->setText(srv.Url); + wOSmServer->authType->setCurrentIndex(static_cast(srv.Type)); wOSmServer->edOsmServerUser->setText(srv.User); wOSmServer->edOsmServerPwd->setText(srv.Password); wOSmServer->rbOsmServerSelected->setChecked(srv.Selected); @@ -352,8 +403,9 @@ void PreferencesDialog::savePrefs() if (!wOsmServer) continue; - OsmServer srv; + OsmServerInfo srv; srv.Url = wOsmServer->edOsmServerUrl->text(); + srv.Type = static_cast(wOsmServer->authType->currentIndex()); srv.User = wOsmServer->edOsmServerUser->text(); srv.Password = wOsmServer->edOsmServerPwd->text(); srv.Selected = wOsmServer->rbOsmServerSelected->isChecked(); diff --git a/src/Preferences/PreferencesDialog.h b/src/Preferences/PreferencesDialog.h index 9f667bd0..740f87d0 100644 --- a/src/Preferences/PreferencesDialog.h +++ b/src/Preferences/PreferencesDialog.h @@ -24,19 +24,6 @@ @author cbro */ -class OsmServerWidget : public QWidget, public Ui::OsmServerWidget -{ - Q_OBJECT - -public: - OsmServerWidget(QWidget * parent = 0, Qt::WindowFlags f = Qt::Widget); - -public slots: - void on_tbOsmServerAdd_clicked(); - void on_tbOsmServerDel_clicked(); - void on_rbOsmServerSelected_clicked(); -}; - class PreferencesDialog : public QDialog, public Ui::PreferencesDialog { Q_OBJECT diff --git a/src/Utils/HttpAuth.cpp b/src/Utils/HttpAuth.cpp index d16958b5..85b970f2 100644 --- a/src/Utils/HttpAuth.cpp +++ b/src/Utils/HttpAuth.cpp @@ -44,13 +44,24 @@ QString generateCodeVerifier() { } HttpAuth::HttpAuth(QObject *parent) : QObject(parent) { - auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); - oauth2.setReplyHandler(replyHandler); - oauth2.setAuthorizationUrl(QUrl("https://www.openstreetmap.org/oauth2/authorize")); - oauth2.setAccessTokenUrl(QUrl("https://www.openstreetmap.org/oauth2/token")); oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); +} +void HttpAuth::setBaseUrl(const QUrl url) { + QUrl base(url); + oauth2.setAuthorizationUrl(base.resolved(QUrl("oauth2/authorize"))); + oauth2.setAccessTokenUrl(base.resolved(QUrl("oauth2/token"))); + qDebug() << "Base URL: " << base; + qDebug() << "Authorization URL: " << oauth2.authorizationUrl(); + qDebug() << "Access Token URL: " << oauth2.accessTokenUrl(); +} + +void HttpAuth::Login() { + auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); + qDebug() << "connect."; + oauth2.setReplyHandler(replyHandler); + qDebug() << "connect2."; codeVerifier = generateCodeVerifier(); codeChallenge = QString(QCryptographicHash::hash(codeVerifier.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); @@ -71,6 +82,8 @@ HttpAuth::HttpAuth(QObject *parent) : QObject(parent) { qDebug() << reply->readAll(); }); + } else if (status == QAbstractOAuth::Status::TemporaryCredentialsReceived) { + qDebug() << "Temporary credentials."; } else { qWarning() << "Status is not granted: " << int(status); emit failed(int(status)); diff --git a/src/Utils/HttpAuth.h b/src/Utils/HttpAuth.h index 02cdf8bc..dcbd6b27 100644 --- a/src/Utils/HttpAuth.h +++ b/src/Utils/HttpAuth.h @@ -12,9 +12,11 @@ class HttpAuth : public QObject public: HttpAuth(QObject *parent = nullptr); + void Login(); - bool isPermanent() const {return false; } - void setPermanent(bool /*value*/) {}; + void setBaseUrl(const QUrl url); + + QString token() const { return oauth2.token(); } public slots: void grant() { oauth2.grant(); }; diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp new file mode 100644 index 00000000..9fe17513 --- /dev/null +++ b/src/Utils/OsmServer.cpp @@ -0,0 +1,204 @@ +#include +#include +#include + +#include "OsmServer.h" +#include "HttpAuth.h" + +class OsmServerImplBasic : public IOsmServerImpl { + public: + OsmServerImplBasic(OsmServerInfo& info, QNetworkAccessManager& manager) + : m_info(info), m_manager(manager) { + } + + QUrl baseUrl() const { + return m_info.Url; + } + + virtual QNetworkReply* get(const QUrl &url) { + return m_manager.get(QNetworkRequest(baseUrl().resolved(url))); + } + + virtual QNetworkReply* put(const QUrl &url, const QByteArray &data) { + return m_manager.put(QNetworkRequest(baseUrl().resolved(url)), data); + } + + virtual QNetworkReply* deleteResource(const QUrl &url) { + return m_manager.deleteResource(QNetworkRequest(baseUrl().resolved(url))); + } + + virtual void authenticate() { + QUrl base(m_info.Url); + auto reply = m_manager.get(QNetworkRequest(base.resolved(QUrl("api/0.6/user/details")))); + connect(reply, &QNetworkReply::finished, [=]() { + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCritical() << "Error:" << reply->errorString(); + emit failed(reply->error()); + } else { + qDebug() << reply->readAll(); + emit authenticated(); + } + }); + } + + private slots: + void on_authenticationRequired( QNetworkReply *reply, QAuthenticator *auth ); + void on_sslErrors(QNetworkReply *reply, const QList& errors); + + private: + OsmServerInfo& m_info; + QNetworkAccessManager& m_manager; +}; + +class OsmServerImplOAuth2 : public IOsmServerImpl { + public: + OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessManager& manager) + : m_info(info), m_manager(manager),m_oauth2(&manager) + { + m_oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); + m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); + } + + QUrl baseUrl() const { + return m_info.Url; + } + + virtual QNetworkReply* get(const QUrl &url) { + return m_oauth2.get(baseUrl().resolved(url)); + } + + virtual QNetworkReply* put(const QUrl &url, const QByteArray &data) { + return m_oauth2.put(baseUrl().resolved(url), data); + } + + virtual QNetworkReply* deleteResource(const QUrl &url) { + return m_oauth2.deleteResource(baseUrl().resolved(url)); + } + + virtual void authenticate(); + + private: + OsmServerInfo& m_info; + QNetworkAccessManager& m_manager; + QOAuth2AuthorizationCodeFlow m_oauth2; + + QString generateCodeVerifier(); +}; + +std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager) { + if (info.Type == OsmServerInfo::AuthType::Basic) { + return std::make_shared(info, manager); + } else if (info.Type == OsmServerInfo::AuthType::OAuth2) { + return std::make_shared(info, manager); + } else { + qWarning() << "Error creating OsmServer: Unknown AuthType" << static_cast(info.Type); + return nullptr; + } +} + +/*********************************************************************** + * OsmServerBasic + **********************************************************************/ + +void OsmServerImplBasic::on_authenticationRequired( QNetworkReply *reply, QAuthenticator *auth ) { + static QNetworkReply *lastReply = NULL; + + /* Only provide authentication the first time we see this reply, to avoid + * infinite loop providing the same credentials. */ + if (lastReply != reply) { + lastReply = reply; + qDebug(/*lc_MerkaartorPreferences */) << "Authentication required and provided."; + auth->setUser(m_info.User); + auth->setPassword(m_info.Password); + } +} + +void OsmServerImplBasic::on_sslErrors(QNetworkReply *reply, const QList& errors) { + Q_UNUSED(reply); + qDebug(/*lc_MerkaartorPreferences*/) << "We stumbled upon some SSL errors: "; + foreach ( QSslError error, errors ) { + qDebug(/*lc_MerkaartorPreferences*/) << "1:"; + qDebug(/*lc_MerkaartorPreferences*/) << error.errorString(); + } +} + +/*********************************************************************** + * OsmServerOAuth2 + **********************************************************************/ + +QString OsmServerImplOAuth2::generateCodeVerifier() { + std::random_device rd; + std::mt19937 gen(rd()); + int bytes = 32; + std::uniform_int_distribution dis(0, 255); + QString result; + for (int i = 0; i < bytes; i++) { + result.append(QString::number(dis(gen), 16)); + } + return result; +} + +void OsmServerImplOAuth2::authenticate() { + m_oauth2.setAuthorizationUrl(baseUrl().resolved(QUrl("oauth2/authorize"))); + m_oauth2.setAccessTokenUrl(baseUrl().resolved(QUrl("oauth2/token"))); + qDebug() << "Base URL: " << baseUrl(); + qDebug() << "Authorization URL: " << m_oauth2.authorizationUrl(); + qDebug() << "Access Token URL: " << m_oauth2.accessTokenUrl(); + + auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); + qDebug() << "connect."; + m_oauth2.setReplyHandler(replyHandler); + qDebug() << "connect2."; + QString codeVerifier = generateCodeVerifier(); + QString codeChallenge = QString(QCryptographicHash::hash(codeVerifier.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=]( + QAbstractOAuth::Status status) { + qDebug() << "OAuth status changed to " << int(status); + if (status == QAbstractOAuth::Status::Granted) { + qDebug() << "Granted, token: " << m_oauth2.token(); + m_info.Password = m_oauth2.token(); + emit authenticated(); + auto reply = get(QUrl("user/details")); + connect(reply, &QNetworkReply::finished, [=]() { + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCritical() << "Error:" << reply->errorString(); + return; + } + + qDebug() << reply->readAll(); + }); + } else if (status == QAbstractOAuth::Status::TemporaryCredentialsReceived) { + qDebug() << "Temporary credentials."; + } else { + qWarning() << "Status is not granted: " << int(status); + emit failed(int(status)); + } + }); + m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap*params){ + qDebug() << "Stage: " << int(stage) << "with params" << *params; + if (params) { + switch (stage) { + case QAbstractOAuth::Stage::RequestingAuthorization: + qDebug() << "Requesting Authorization."; + params->insert("code_challenge", codeChallenge); + params->insert("code_challenge_method", "S256"); + break; + case QAbstractOAuth::Stage::RequestingAccessToken: + qDebug() << "Requesting Access Token."; + params->insert("code_verifier", codeVerifier); + break; + default: + qDebug() << "default stage."; + } + } + qDebug() << "Stage: " << int(stage) << "with params" << *params; + }); + + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, + &QDesktopServices::openUrl); +} diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h new file mode 100644 index 00000000..8e58e8ff --- /dev/null +++ b/src/Utils/OsmServer.h @@ -0,0 +1,82 @@ +#ifndef __OSMSERVER_H__ +#define __OSMSERVER_H__ + +#include +#include +#include + +#include + +class QNetworkAccessManager; + +// A dataclass for OSM server information +struct OsmServerInfo +{ + /* Note: Types here correspond to the UI combo box. Keep those in sync. */ + enum class AuthType : int{ + Basic, + OAuth2 //password field is the token if this type is used + }; + + static AuthType typeFromString(const QString& type) { + if (type == "basic") return AuthType::Basic; + if (type == "oauth2") return AuthType::OAuth2; + qWarning() << "Error parsing AuthType: Unknown AuthType" << type; + return AuthType::Basic; + } + + static QString typeToString(AuthType type) { + switch (type) { + case AuthType::OAuth2: return "oauth2"; + case AuthType::Basic: return "basic"; + default: + qWarning() << "Error encoding AuthType: Unknown AuthType" << static_cast(type); + return "basic"; + } + } + + bool Selected; + AuthType Type; + QString Url; + QString User; + QString Password; +}; + +class IOsmServerImpl : public QObject { + Q_OBJECT + + public: + virtual QNetworkReply* get(const QUrl &url) = 0; + virtual QNetworkReply* put(const QUrl &url, const QByteArray &data) = 0; + virtual QNetworkReply* deleteResource(const QUrl &url) = 0; + virtual void authenticate() = 0; + signals: + void authenticated(); + void failed(int error); +}; + +std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager); + +class OsmServer : public IOsmServerImpl { + Q_OBJECT + + public: + OsmServer(QNetworkAccessManager& manager); + + void setInfo(OsmServerInfo& info) { + m_info = info; + m_impl = makeOsmServer(m_info, m_manager); + } + + virtual QNetworkReply* get(const QUrl &url); + virtual QNetworkReply* put(const QUrl &url, const QByteArray &data); + virtual QNetworkReply* deleteResource(const QUrl &url); + virtual void authenticate(); + + private: + OsmServerInfo& m_info; + QNetworkAccessManager& m_manager; + std::shared_ptr m_impl; +}; + +#endif diff --git a/tests/test-HttpAuth.cpp b/tests/test-HttpAuth.cpp index 20df44fa..d721d8d6 100644 --- a/tests/test-HttpAuth.cpp +++ b/tests/test-HttpAuth.cpp @@ -9,7 +9,11 @@ class TestHttpAuth : public QObject void simpleLogin() { + QUrl url("hello"); + qDebug() << url; + HttpAuth auth(this); + auth.Login(); auth.grant(); QTest::qWait(10000); } From f6031de8f8ff50f91358feee94b1a8f49496f5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Fri, 5 Jul 2024 21:44:21 +0200 Subject: [PATCH 04/30] Basic refactoring done, download working. --- src/Docks/InfoDock.cpp | 2 +- src/ImportExport/ImportOSM.cpp | 2 +- src/Layers/Layer.cpp | 2 +- src/MainWindow.cpp | 34 +--- src/MainWindow.h | 5 +- src/Preferences/MerkaartorPreferences.cpp | 132 +++---------- src/Preferences/MerkaartorPreferences.h | 20 +- src/Preferences/PreferencesDialog.cpp | 28 ++- src/Sync/DirtyListExecutorOSC.cpp | 10 +- src/Sync/DirtyListExecutorOSC.h | 5 +- src/Sync/DownloadOSM.cpp | 215 ++++++---------------- src/Sync/DownloadOSM.h | 10 +- src/Utils/OsmServer.cpp | 20 +- src/Utils/OsmServer.h | 31 +--- src/common/GotoDialog.cpp | 2 +- 15 files changed, 156 insertions(+), 362 deletions(-) diff --git a/src/Docks/InfoDock.cpp b/src/Docks/InfoDock.cpp index 744e880a..86682147 100644 --- a/src/Docks/InfoDock.cpp +++ b/src/Docks/InfoDock.cpp @@ -80,7 +80,7 @@ void InfoDock::on_anchorClicked(const QUrl & link) // } else { // QMessageBox::warning(Main,QApplication::translate("Downloader","Download failed"),QApplication::translate("Downloader","Unexpected http status code (%1)").arg(theDownloader.resultCode())); // } - QUrl theUrl(M_PREFS->getOsmWebsite()+link.path()); + QUrl theUrl(M_PREFS->getOsmServer()->getServerInfo().Url+link.path()); QDesktopServices::openUrl(theUrl); } diff --git a/src/ImportExport/ImportOSM.cpp b/src/ImportExport/ImportOSM.cpp index 3caf958d..94da5850 100644 --- a/src/ImportExport/ImportOSM.cpp +++ b/src/ImportExport/ImportOSM.cpp @@ -343,7 +343,7 @@ static bool downloadToResolve(const QList& Resolution, QWidget* aParen for (int i=0; igetOsmApiUrl()+theDownloader->getURLToFetchFull(Resolution[i]); + QUrl URL = theDownloader->getURLToFetchFull(Resolution[i]); Lbl->setText(QApplication::translate("Downloader","Downloading unresolved %1 of %2").arg(i+1).arg(Resolution.size())); if (theDownloader->go(URL)) { diff --git a/src/Layers/Layer.cpp b/src/Layers/Layer.cpp index 1337fc7c..5cbbeb5d 100644 --- a/src/Layers/Layer.cpp +++ b/src/Layers/Layer.cpp @@ -831,7 +831,7 @@ LayerWidget* SpecialLayer::newWidget(void) void SpecialLayer::refreshLayer() { if (m_type == Layer::MapDustLayer) { - g_Merk_MainWindow->on_layersMapdustAction_triggered(); + /* FIXME: Remove mapdust type */ } } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 520f8de7..96169e51 100755 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -102,6 +102,7 @@ #include "gdal_version.h" #include "Utils/SlippyMapWidget.h" +#include "Utils/OsmServer.h" namespace { @@ -2018,6 +2019,8 @@ void MainWindow::on_fileUploadAction_triggered() return; } + /* TODO: Check if the server list is empty? */ + /* while (M_PREFS->getOsmUser().isEmpty()) { int ret = QMessageBox::warning(this, tr("Upload OSM"), tr("You don't seem to have specified your\n" "OpenStreetMap username and password.\nDo you want to do this now?"), QMessageBox::Yes | QMessageBox::No); @@ -2026,9 +2029,10 @@ void MainWindow::on_fileUploadAction_triggered() } else return; } + */ on_editPropertiesAction_triggered(); // TODO: Replace this call to use a the OsmServer object instead of individual parameters. - syncOSM(M_PREFS->getOsmApiUrl(), M_PREFS->getOsmUser(), M_PREFS->getOsmPassword()); + syncOSM(M_PREFS->getOsmServer()); theDocument->history().updateActions(); theDirty->updateList(); @@ -2068,28 +2072,6 @@ void MainWindow::on_fileDownloadMoreAction_triggered() emit content_changed(); } -void MainWindow::on_layersMapdustAction_triggered() -{ - SpecialLayer* sl = NULL; - for (int i=0; ilayerSize(); ++i) { - if (theDocument->getLayer(i)->classType() == Layer::MapDustLayer) { - sl = dynamic_cast(theDocument->getLayer(i)); - while (sl->size()) - { - sl->deleteFeature(sl->get(0)); - } - } - } - - createProgressDialog(); - - if (!::downloadMapdust(this, theView->viewport(), theDocument, sl)) { - QMessageBox::warning(this, tr("Error downloading MapDust"), tr("The MapDust bugs could not be downloaded")); - } - - deleteProgressDialog(); -} - void MainWindow::downloadFeatures(const QList& aDownloadList) { createProgressDialog(); @@ -4295,7 +4277,7 @@ bool MainWindow::hasUnsavedChanges() return true; } -void MainWindow::syncOSM(const QString& aWeb, const QString& aUser, const QString& aPwd) +void MainWindow::syncOSM(OsmServer server) { #ifndef FRISIUS_BUILD if (checkForConflicts(theDocument)) { @@ -4306,10 +4288,10 @@ void MainWindow::syncOSM(const QString& aWeb, const QString& aUser, const QStrin DirtyListBuild Future; theDocument->history().buildDirtyList(Future); DirtyListDescriber Describer(theDocument,Future); - ChangesetInfo changesetInfo = {aWeb, "", ""}; + ChangesetInfo changesetInfo = {server->getServerInfo().Url, "", ""}; if (Describer.showChanges(this, changesetInfo) && Describer.tasks()) { Future.resetUpdates(); - DirtyListExecutorOSC Exec(theDocument,Future,changesetInfo,aWeb,aUser,aPwd,Describer.tasks()); + DirtyListExecutorOSC Exec(theDocument,Future,changesetInfo,server,Describer.tasks()); if (Exec.executeChanges(this)) { if (M_PREFS->getAutoHistoryCleanup() && !theDocument->getDirtyOrOriginLayer()->getDirtySize()) theDocument->history().cleanup(); diff --git a/src/MainWindow.h b/src/MainWindow.h index 3e40e210..9b2ce1e3 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -9,6 +9,8 @@ #include #include +#include "OsmServer.h" + namespace Ui { class MainWindow; } @@ -77,7 +79,6 @@ public slots: virtual void on_layersNewImageAction_triggered(); virtual void on_layersNewDrawingAction_triggered(); virtual void on_layersNewFilterAction_triggered(); - virtual void on_layersMapdustAction_triggered(); virtual void on_fileNewAction_triggered(); virtual void on_fileDownloadAction_triggered(); @@ -340,7 +341,7 @@ private slots: void startBusyCursor(); void endBusyCursor(); - void syncOSM(const QString &aWeb, const QString &aUser, const QString &aPwd); + void syncOSM(OsmServer srv); protected: void closeEvent(QCloseEvent * event); virtual QMenu * createPopupMenu (); diff --git a/src/Preferences/MerkaartorPreferences.cpp b/src/Preferences/MerkaartorPreferences.cpp index 4954c37b..c7063a70 100644 --- a/src/Preferences/MerkaartorPreferences.cpp +++ b/src/Preferences/MerkaartorPreferences.cpp @@ -227,6 +227,7 @@ Tool::Tool() namespace { + QSettings* getSettings() { if (!g_Merk_Portable) { return new QSettings(); @@ -253,9 +254,7 @@ MerkaartorPreferences::MerkaartorPreferences() version = Sets->value("version/version", "0").toString(); } - connect(&httpRequest, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, SLOT(on_authenticationRequired(QNetworkReply*, QAuthenticator*))); connect(&httpRequest, SIGNAL(finished(QNetworkReply*)),this,SLOT(on_requestFinished(QNetworkReply*))); - connect(&httpRequest, SIGNAL(sslErrors(QNetworkReply *, const QList&)), this, SLOT(on_sslErrors(QNetworkReply*, const QList&))); #ifdef USE_LIBPROXY // Initialise libproxy @@ -308,6 +307,7 @@ void MerkaartorPreferences::save(bool UserPwdChanged) void MerkaartorPreferences::toOsmPref() { + /* FIXME: Refactor. qDebug(lc_MerkaartorPreferences) << "MerkaartorPreferences::toOsmPref"; if (getOfflineMode()) return; @@ -356,10 +356,12 @@ void MerkaartorPreferences::toOsmPref() putOsmPref(it.key(), it.value()); } + */ } void MerkaartorPreferences::fromOsmPref() { + /* FIXME: Refactor. if (getOfflineMode()) return; qDebug(lc_MerkaartorPreferences) << "Requesting preferences from OSM server."; @@ -373,35 +375,23 @@ void MerkaartorPreferences::fromOsmPref() httpRequest.setProxy(getProxy(osmWeb)); OsmPrefLoadReply = httpRequest.get(req); -} - -void MerkaartorPreferences::on_authenticationRequired( QNetworkReply *reply, QAuthenticator *auth ) { - static QNetworkReply *lastReply = NULL; - - /* Only provide authentication the first time we see this reply, to avoid - * infinite loop providing the same credentials. */ - if (lastReply != reply) { - lastReply = reply; - qDebug(lc_MerkaartorPreferences) << "Authentication required and provided."; - auth->setUser(getOsmUser()); - auth->setPassword(getOsmPassword()); - } -} - -void MerkaartorPreferences::on_sslErrors(QNetworkReply *reply, const QList& errors) { - Q_UNUSED(reply); - qDebug(lc_MerkaartorPreferences) << "We stumbled upon some SSL errors: "; - foreach ( QSslError error, errors ) { - qDebug(lc_MerkaartorPreferences) << "1:"; - qDebug(lc_MerkaartorPreferences) << error.errorString(); - } + */ } void MerkaartorPreferences::on_requestFinished ( QNetworkReply *reply ) { int error = reply->error(); + + if (reply != OsmPrefLoadReply) { + qWarning(lc_MerkaartorPreferences) << "Ignoring foreign network request with URL: " << reply->url(); + return; + } + if (error != QNetworkReply::NoError) { - //qDebug(lc_MerkaartorPreferences) << "Received response with code " << error << "(" << reply->errorString() << ")"; + qDebug(lc_MerkaartorPreferences) << "Error: " << reply->errorString(); + qDebug(lc_MerkaartorPreferences) << "Received response with code " << error; + qDebug(lc_MerkaartorPreferences) << "Url was:" << reply->url(); + switch (error) { case QNetworkReply::HostNotFoundError: qWarning() << "MerkaartorPreferences: Host not found, preferences won't be synchronized with your profile."; @@ -420,9 +410,6 @@ void MerkaartorPreferences::on_requestFinished ( QNetworkReply *reply ) } } - if (reply != OsmPrefLoadReply) - return; - qDebug(lc_MerkaartorPreferences) << "Reading preferences from online profile."; QDomDocument aOsmPrefDoc; @@ -508,6 +495,7 @@ void MerkaartorPreferences::on_requestFinished ( QNetworkReply *reply ) void MerkaartorPreferences::putOsmPref(const QString& k, const QString& v) { + /* FIXME: Refactor. qDebug(lc_MerkaartorPreferences) << "Saving OSM preference online: " << k << "=" << v; QUrl osmWeb(getOsmApiUrl()+QString("/user/preferences/%1").arg(k)); @@ -518,10 +506,12 @@ void MerkaartorPreferences::putOsmPref(const QString& k, const QString& v) httpRequest.setProxy(getProxy(osmWeb)); OsmPrefSaveReply = httpRequest.put(req, ba); + */ } void MerkaartorPreferences::deleteOsmPref(const QString& k) { + /* FIXME: Refactor. qDebug(lc_MerkaartorPreferences) << "Deleting OSM preference online: " << k; QUrl osmWeb(getOsmApiUrl()+QString("/user/preferences/%1").arg(k)); @@ -530,6 +520,7 @@ void MerkaartorPreferences::deleteOsmPref(const QString& k) httpRequest.setProxy(getProxy(osmWeb)); OsmPrefSaveReply = httpRequest.sendCustomRequest(req,"DELETE"); + */ } void MerkaartorPreferences::initialize() @@ -948,95 +939,20 @@ M_PARAM_IMPLEMENT_BOOL(HideToolbarLabels, interface, false) /* DATA */ -QString MerkaartorPreferences::getOsmWebsite() const -{ - QString s; - if (!g_Merk_Ignore_Preferences && !g_Merk_Reset_Preferences) - s = Sets->value("osm/Website", "www.openstreetmap.org").toString(); - else - s = "www.openstreetmap.org"; +M_PARAM_IMPLEMENT_INT(OsmServerIndex, osm, 0) -#if QT_VERSION >= 0x040600 && !defined(FORCE_46) - QUrl u = QUrl::fromUserInput(s); -#else - // convenience for creating a valid URL - // fails miserably if QString s already contains a schema - QString h = s; // intermediate host - QString p; // intermediate path - - int slashpos = s.indexOf('/'); - if (slashpos >= 1) // there's a path element in s - { - h = s.left(slashpos); - p = s.right(s.size() - 1 - slashpos); - } - - QUrl u; - u.setHost(h); - u.setScheme("http"); - u.setPath(p); -#endif - - if (!u.path().isEmpty()) - u.setPath(QString()); - - return u.toString(); -} - - -QString MerkaartorPreferences::getOsmApiUrl() const -{ - QUrl u(getOsmWebsite()); - if (u.path().isEmpty()) - u.setPath("/api/" + apiVersion()); - - qDebug() << "getOsmApiUrl" << u.toString(); - return u.toString(); -} - -void MerkaartorPreferences::setOsmWebsite(const QString & theValue) -{ - if (!g_Merk_Ignore_Preferences) - Sets->setValue("osm/Website", theValue); +std::shared_ptr MerkaartorPreferences::getOsmServer() { + /* TODO: Each call creates a new server. */ + OsmServerList* servers = getOsmServers(); + return makeOsmServer((*servers)[getOsmServerIndex()], httpRequest); } M_PARAM_IMPLEMENT_STRING(XapiUrl, osm, "http://www.overpass-api.de/api/xapi_meta?") M_PARAM_IMPLEMENT_STRING(NominatimUrl, osm, "http://nominatim.openstreetmap.org/search") M_PARAM_IMPLEMENT_BOOL(AutoHistoryCleanup, data, true); - -QString MerkaartorPreferences::getOsmUser() const -{ - if (!g_Merk_Ignore_Preferences && !g_Merk_Reset_Preferences) - return Sets->value("osm/User").toString(); - else - return QString(); -} - -void MerkaartorPreferences::setOsmUser(const QString & theValue) -{ - if (!g_Merk_Ignore_Preferences) - Sets->setValue("osm/User", theValue); -} - -QString MerkaartorPreferences::getOsmPassword() const -{ - if (!g_Merk_Ignore_Preferences && !g_Merk_Reset_Preferences) - return Sets->value("osm/Password").toString(); - else - return QString(); -} - -void MerkaartorPreferences::setOsmPassword(const QString & theValue) -{ - if (!g_Merk_Ignore_Preferences) - Sets->setValue("osm/Password", theValue); -} - M_PARAM_IMPLEMENT_DOUBLE(MaxDistNodes, data, 0.0); - M_PARAM_IMPLEMENT_BOOL(AutoSaveDoc, data, false); M_PARAM_IMPLEMENT_BOOL(AutoExtractTracks, data, false); - M_PARAM_IMPLEMENT_INT(DirectionalArrowsVisible, visual, 1); RendererOptions MerkaartorPreferences::getRenderOptions() diff --git a/src/Preferences/MerkaartorPreferences.h b/src/Preferences/MerkaartorPreferences.h index af4fc146..8f11bbf7 100644 --- a/src/Preferences/MerkaartorPreferences.h +++ b/src/Preferences/MerkaartorPreferences.h @@ -234,19 +234,21 @@ Q_OBJECT M_PARAM_DECLARE_BOOL(HideToolbarLabels) /* Data */ - void setOsmWebsite(const QString & theValue); - QString getOsmWebsite() const; - QString getOsmApiUrl() const; +// void setOsmWebsite(const QString & theValue); +// QString getOsmWebsite() const; +// QString getOsmApiUrl() const; + M_PARAM_DECLARE_INT(OsmServerIndex) + std::shared_ptr getOsmServer(); M_PARAM_DECLARE_STRING(XapiUrl) M_PARAM_DECLARE_STRING(NominatimUrl) M_PARAM_DECLARE_BOOL(AutoHistoryCleanup) - void setOsmUser(const QString & theValue); - QString getOsmUser() const; - - void setOsmPassword(const QString & theValue); - QString getOsmPassword() const; +// void setOsmUser(const QString & theValue); +// QString getOsmUser() const; +// +// void setOsmPassword(const QString & theValue); +// QString getOsmPassword() const; M_PARAM_DECLARE_DOUBLE(MaxDistNodes) @@ -486,8 +488,6 @@ Q_OBJECT private slots: void on_requestFinished ( QNetworkReply *reply ); - void on_authenticationRequired( QNetworkReply *reply, QAuthenticator *auth ); - void on_sslErrors(QNetworkReply *reply, const QList& errors); signals: void bookmarkChanged(); diff --git a/src/Preferences/PreferencesDialog.cpp b/src/Preferences/PreferencesDialog.cpp index 3b8c96c1..86d23770 100644 --- a/src/Preferences/PreferencesDialog.cpp +++ b/src/Preferences/PreferencesDialog.cpp @@ -16,7 +16,8 @@ #include "Document.h" #include "Feature.h" #include "PropertiesDock.h" -#include "HttpAuth.h" +//#include "HttpAuth.h" +#include "OsmServer.h" #include #include @@ -54,7 +55,7 @@ public slots: void on_httpAuth_authenticated(); void on_httpAuth_failed(); private: - HttpAuth httpAuth; + std::shared_ptr m_impl; }; #include "PreferencesDialog.moc" @@ -65,8 +66,8 @@ OsmServerWidget::OsmServerWidget(QWidget * parent, Qt::WindowFlags f) setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - connect(&httpAuth, &HttpAuth::authenticated, this, &OsmServerWidget::on_httpAuth_authenticated); - connect(&httpAuth, &HttpAuth::failed, this, &OsmServerWidget::on_httpAuth_failed); + connect(&*m_impl, &IOsmServerImpl::authenticated, this, &OsmServerWidget::on_httpAuth_authenticated); + connect(&*m_impl, &IOsmServerImpl::failed, this, &OsmServerWidget::on_httpAuth_failed); } void OsmServerWidget::on_tbOsmServerAdd_clicked() @@ -125,17 +126,14 @@ void OsmServerWidget::on_rbOsmServerSelected_clicked() void OsmServerWidget::on_tbOAuth2Login_clicked() { qDebug() << "Start login..."; - httpAuth.setBaseUrl(QUrl(this->edOsmServerUrl->text()).resolved(QUrl("/"))); - httpAuth.Login(); - httpAuth.grant(); + //httpAuth.setBaseUrl(QUrl(this->edOsmServerUrl->text()).resolved(QUrl("/"))); + m_impl->authenticate(); this->lbLoginState->setText(tr("Authenticating")); this->tbOAuth2Login->setEnabled(false); } void OsmServerWidget::on_httpAuth_authenticated() { qDebug() << "Authenticated!"; - this->edOsmServerUser->setText("oauth2"); - this->edOsmServerPwd->setText(httpAuth.token()); this->lbLoginState->setText(tr("Authenticated")); this->tbOAuth2Login->setEnabled(true); } @@ -237,10 +235,10 @@ void PreferencesDialog::loadPrefs() if (!theOsmServers->size()) { OsmServerWidget* wOSmServer = new OsmServerWidget(grpOSM); - wOSmServer->edOsmServerUrl->setText(M_PREFS->getOsmApiUrl()); + wOSmServer->edOsmServerUrl->setText(""); // wOSmServer->authType->setCurrentIndex(0); // Keep default authType per UI. - wOSmServer->edOsmServerUser->setText(M_PREFS->getOsmUser()); - wOSmServer->edOsmServerPwd->setText(M_PREFS->getOsmPassword()); + wOSmServer->edOsmServerUser->setText(""); + wOSmServer->edOsmServerPwd->setText(""); wOSmServer->rbOsmServerSelected->setChecked(true); wOSmServer->tbOsmServerDel->setEnabled(false); @@ -410,10 +408,8 @@ void PreferencesDialog::savePrefs() srv.Password = wOsmServer->edOsmServerPwd->text(); srv.Selected = wOsmServer->rbOsmServerSelected->isChecked(); - if (srv.Selected && (srv.Url != M_PREFS->getOsmApiUrl() || srv.User != M_PREFS->getOsmUser() || srv.Password != M_PREFS->getOsmPassword())) { - M_PREFS->setOsmWebsite(srv.Url); - M_PREFS->setOsmUser(srv.User); - M_PREFS->setOsmPassword(srv.Password); + if (srv.Selected) { + M_PREFS->setOsmServerIndex(i); OsmDataChanged = true; } diff --git a/src/Sync/DirtyListExecutorOSC.cpp b/src/Sync/DirtyListExecutorOSC.cpp index 1a9bfea6..74766940 100644 --- a/src/Sync/DirtyListExecutorOSC.cpp +++ b/src/Sync/DirtyListExecutorOSC.cpp @@ -34,10 +34,10 @@ DirtyListExecutorOSC::DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& { } -DirtyListExecutorOSC::DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture, const ChangesetInfo& info, const QString& aWeb, const QString& aUser, const QString& aPwd, int aTasks) -: DirtyListVisit(aDoc, aFuture, false), changesetInfo(info), Tasks(aTasks), Done(0), Web(aWeb), User(aUser), Pwd(aPwd), theDownloader(0) +DirtyListExecutorOSC::DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture, const ChangesetInfo& info, OsmServer server, int aTasks) +: DirtyListVisit(aDoc, aFuture, false), changesetInfo(info), Tasks(aTasks), Done(0), server(server), theDownloader(0) { - theDownloader = new Downloader(User, Pwd); + theDownloader = new Downloader(server); } DirtyListExecutorOSC::~DirtyListExecutorOSC() @@ -55,7 +55,7 @@ int DirtyListExecutorOSC::sendRequest(const QString& Method, const QString& URL, QMessageBox::StandardButton theChoice = QMessageBox::Retry; while (theChoice == QMessageBox::Retry) { - QUrl theUrl(Web+URL); + QUrl theUrl(URL); if (!theDownloader->request(Method,theUrl,Data)) { qDebug() << QString("Upload error: request (%1); Server message is '%2'").arg(theDownloader->resultCode()).arg(theDownloader->resultText()); @@ -322,7 +322,7 @@ bool DirtyListExecutorOSC::stop() QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents); URL = theDownloader->getURLToCloseChangeSet(ChangeSetId); - QUrl theUrl(Web+URL); + QUrl theUrl(URL); theDownloader->setAnimator(NULL, NULL, NULL, false); if (!theDownloader->request("PUT",theUrl,DataIn)) { QMessageBox::warning(NULL, tr("Changeset could not be closed."), tr("An unknown error has occurred. It might already be closed, or will be closed automatically. If you want to be sure, please, check manually on the osm.org website.")); diff --git a/src/Sync/DirtyListExecutorOSC.h b/src/Sync/DirtyListExecutorOSC.h index 0cc30d6a..57923095 100644 --- a/src/Sync/DirtyListExecutorOSC.h +++ b/src/Sync/DirtyListExecutorOSC.h @@ -17,6 +17,7 @@ #include #include +#include "Utils/OsmServer.h" class Downloader; @@ -26,7 +27,7 @@ class DirtyListExecutorOSC : public QObject, public DirtyListVisit public: DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture); - DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture, const ChangesetInfo& info, const QString& aWeb, const QString& aUser, const QString& aPwd, int aTasks); + DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture, const ChangesetInfo& info, OsmServer server, int aTasks); virtual ~DirtyListExecutorOSC(); void OscCreate(Feature* F); @@ -57,7 +58,7 @@ class DirtyListExecutorOSC : public QObject, public DirtyListVisit Ui::SyncListDialog Ui; int Tasks, Done; QProgressDialog* Progress; - QString Web,User,Pwd; + OsmServer server; Downloader* theDownloader; QString ChangeSetId; QString LastAction; diff --git a/src/Sync/DownloadOSM.cpp b/src/Sync/DownloadOSM.cpp index b1be43e7..4cf8c126 100644 --- a/src/Sync/DownloadOSM.cpp +++ b/src/Sync/DownloadOSM.cpp @@ -30,13 +30,12 @@ /* DOWNLOADER */ -Downloader::Downloader(const QString& aUser, const QString& aPwd) -: User(aUser), Password(aPwd), +Downloader::Downloader(OsmServer server) +: server(server), currentReply(0),Error(false), AnimatorLabel(0), AnimatorBar(0), AnimationTimer(0) { - //IdAuth = Request.setUser(User.toUtf8(), Password.toUtf8()); - connect(&netManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(on_requestFinished(QNetworkReply*))); - connect(&netManager,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this,SLOT(on_authenticationRequired(QNetworkReply*,QAuthenticator*))); + /* FIXME: Handle finished request. */ + //connect(&netManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(on_requestFinished(QNetworkReply*))); } @@ -66,14 +65,6 @@ void Downloader::setAnimator(QProgressDialog *anAnimator, QLabel* anAnimatorLabe } } -void Downloader::on_Cancel_clicked() -{ - Error = true; - if (Loop.isRunning()) - Loop.exit(QDialog::Rejected); -} - -#include "QTextBrowser" bool Downloader::go(const QUrl& url) { return request("GET", url, QString()); @@ -82,17 +73,28 @@ bool Downloader::go(const QUrl& url) { bool Downloader::request(const QString& theMethod, const QUrl& url, const QString& theData) { if (Error) return false; - qDebug() << "Downloader::request:" << url; + qDebug() << "Downloader::request:" << server->baseUrl().resolved(url); - netManager.setProxy(M_PREFS->getProxy(url)); - QNetworkRequest req(url); + QNetworkRequest req(server->baseUrl().resolved(url)); req.setRawHeader(QByteArray("Content-Type"), QByteArray("text/xml")); req.setRawHeader(QByteArray("User-Agent"), USER_AGENT.toLatin1()); QByteArray dataArray(theData.toUtf8()); - QBuffer dataBuffer(&dataArray); - currentReply = netManager.sendCustomRequest(req, theMethod.toLatin1(), &dataBuffer); + currentReply = server->sendRequest(req, theMethod.toLatin1(), dataArray); + connect(currentReply, &QNetworkReply::finished, this, [this] { + qDebug() << "Downloader::request: received response"; + if (currentReply->error()) { + Error = true; + qDebug() << "Downloader::request: received response with code" + << currentReply->error() << ", message" << currentReply->errorString(); + qDebug() << "Body: " << currentReply->readAll(); + Loop.exit(QDialog::Rejected); + } else { + Loop.exit(QDialog::Accepted); + } + } + ); if (AnimationTimer) { AnimationTimer->start(200); @@ -130,32 +132,11 @@ bool Downloader::request(const QString& theMethod, const QUrl& url, const QStrin return !Error; } -void Downloader::on_authenticationRequired( QNetworkReply *reply, QAuthenticator *auth) { - static QNetworkReply *lastReply = NULL; - - /* Only provide authentication the first time we see this reply, to avoid - * infinite loop providing the same credentials. */ - if (lastReply != reply) { - lastReply = reply; - qDebug() << "Downloader authentication required and provided."; - auth->setUser(User); - auth->setPassword(Password); - } -} - QByteArray& Downloader::content() { return Content; } -void Downloader::on_requestFinished( QNetworkReply *reply) -{ - if (reply->error()) - Error = true; - if ( (reply == currentReply) && Loop.isRunning() ) - Loop.exit(QDialog::Accepted); -} - void Downloader::progress(qint64 done, qint64 total) { if (AnimatorLabel && AnimatorBar) @@ -196,22 +177,22 @@ const QString &Downloader::locationText() QString Downloader::getURLToOpenChangeSet() { - return QString("/changeset/create"); + return QString("/api/0.6/changeset/create"); } QString Downloader::getURLToCloseChangeSet(const QString& Id) { - return QString("/changeset/%1/close").arg(Id); + return QString("/api/0.6/changeset/%1/close").arg(Id); } QString Downloader::getURLToUploadDiff(QString changesetId) { - return QString("/changeset/%1/upload").arg(changesetId); + return QString("/api/0.6/changeset/%1/upload").arg(changesetId); } QString Downloader::getURLToFetch(const QString &What) { - QString URL = QString("/%1?%2="); + QString URL = QString("/api/0.6/%1?%2="); return URL.arg(What).arg(What); } @@ -221,13 +202,13 @@ QString Downloader::getURLToFetchFull(IFeature::FId id) QString URL; if (id.type & IFeature::Point) { type = "node"; - URL = QString("/%1/%2"); + URL = QString("/api/0.6/%1/%2"); } else { if (id.type & IFeature::LineString) type = "way"; if (id.type & IFeature::OsmRelation) type = "relation"; - URL = QString("/%1/%2/full"); + URL = QString("/api/0.6/%1/%2/full"); } return URL.arg(type).arg(id.numId); @@ -240,43 +221,43 @@ QString Downloader::getURLToFetchFull(Feature* aFeature) QString Downloader::getURLToFetch(const QString &What, const QString& Id) { - QString URL = QString("/%1/%2"); + QString URL = QString("/api/0.6/%1/%2"); return URL.arg(What).arg(Id); } QString Downloader::getURLToCreate(const QString &What) { - QString URL = QString("/%1/create"); + QString URL = QString("/api/0.6/%1/create"); return URL.arg(What); } QString Downloader::getURLToUpdate(const QString &What, const QString& Id) { - QString URL = QString("/%1/%2"); + QString URL = QString("/api/0.6/%1/%2"); return URL.arg(What).arg(Id); } QString Downloader::getURLToDelete(const QString &What, const QString& Id) { - QString URL = QString("/%1/%2"); + QString URL = QString("/api/0.6/%1/%2"); return URL.arg(What).arg(Id); } QString Downloader::getURLToMap() { - QString URL("/map?bbox=%1,%2,%3,%4"); + QString URL("/api/0.6/map?bbox=%1,%2,%3,%4"); return URL; } QString Downloader::getURLToTrackPoints() { - QString URL = QString("/trackpoints?bbox=%1,%2,%3,%4&page=%5"); + QString URL = QString("/api/0.6/trackpoints?bbox=%1,%2,%3,%4&page=%5"); return URL; } -bool downloadOSM(QWidget* aParent, const QUrl& theUrl, const QString& aUser, const QString& aPassword, Document* theDocument, Layer* theLayer) +bool downloadOSM(QWidget* aParent, const QUrl& theUrl, OsmServer server, Document* theDocument, Layer* theLayer) { - Downloader Rcv(aUser, aPassword); + Downloader Rcv(server); IProgressWindow* aProgressWindow = dynamic_cast(aParent); if (aProgressWindow) { @@ -319,6 +300,8 @@ bool downloadOSM(QWidget* aParent, const QUrl& theUrl, const QString& aUser, con case 301: case 302: case 307: { + qDebug() << "FIXME: Relocation to: " << Rcv.locationText(); + /* QString aWeb = Rcv.locationText(); if (!aWeb.isEmpty()) { QUrl aURL(aWeb); @@ -330,6 +313,7 @@ bool downloadOSM(QWidget* aParent, const QUrl& theUrl, const QString& aUser, con QMessageBox::warning(aParent,QApplication::translate("Downloader","Download failed"), msg); return false; } + */ break; } case 401: @@ -342,19 +326,19 @@ bool downloadOSM(QWidget* aParent, const QUrl& theUrl, const QString& aUser, con QMessageBox::warning(aParent,QApplication::translate("Downloader","Download failed"), msg); return false; } - Downloader Down(aUser, aPassword); + Downloader Down(server); bool OK = importOSM(aParent, Rcv.content(), theDocument, theLayer, &Down); return OK; } -bool downloadOSM(QWidget* aParent, const QString& aWeb, const QString& aUser, const QString& aPassword, const CoordBox& aBox , Document* theDocument, Layer* theLayer) +bool downloadOSM(QWidget* aParent, OsmServer server, const CoordBox& aBox , Document* theDocument, Layer* theLayer) { if (checkForConflicts(theDocument)) { QMessageBox::warning(aParent,QApplication::translate("Downloader","Unresolved conflicts"), QApplication::translate("Downloader","Please resolve existing conflicts first")); return false; } - Downloader Rcv(aUser, aPassword); + Downloader Rcv(server); QString URL = Rcv.getURLToMap(); if ((fabs(aBox.bottomLeft().x()) < 180.0 && fabs(aBox.topRight().x()) > 180.0) @@ -371,20 +355,20 @@ bool downloadOSM(QWidget* aParent, const QString& aWeb, const QString& aUser, co q2.setRight(-180*sign); q2.setLeft(q2.left()+360); } - return downloadOSM(aParent, aWeb, aUser, aPassword, q1, theDocument, theLayer) - && downloadOSM(aParent, aWeb, aUser, aPassword, q2, theDocument, theLayer); + return downloadOSM(aParent, server, q1, theDocument, theLayer) + && downloadOSM(aParent, server, q2, theDocument, theLayer); } else { /* Normal code path */ URL = URL.arg(aBox.bottomLeft().x(), 0, 'f').arg(aBox.bottomLeft().y(), 0, 'f').arg(aBox.topRight().x(), 0, 'f').arg(aBox.topRight().y(), 0, 'f'); - QUrl theUrl(aWeb+URL); - return downloadOSM(aParent, theUrl, aUser, aPassword, theDocument, theLayer); + QUrl theUrl(URL); + return downloadOSM(aParent, theUrl, server, theDocument, theLayer); } } -bool downloadTracksFromOSM(QWidget* Main, const QString& aWeb, const QString& aUser, const QString& aPassword, const CoordBox& aBox , Document* theDocument) +bool downloadTracksFromOSM(QWidget* Main, OsmServer server, const CoordBox& aBox , Document* theDocument) { - Downloader theDownloader(aUser, aPassword); + Downloader theDownloader(server); QList theTracklayers; //TrackMapLayer* trackLayer = new TrackMapLayer(QApplication::translate("Downloader","Downloaded tracks")); //theDocument->add(trackLayer); @@ -416,7 +400,7 @@ bool downloadTracksFromOSM(QWidget* Main, const QString& aWeb, const QString& aU arg(aBox.topRight().x()). arg(aBox.topRight().y()). arg(Page); - QUrl theUrl(aWeb+URL); + QUrl theUrl(URL); if (!theDownloader.go(theUrl)) return false; if (theDownloader.resultCode() != 200) @@ -473,23 +457,18 @@ bool downloadFeatures(MainWindow* Main, const QList& idList , Doc theLayer = (Layer*)theDocument->getLastDownloadLayer(); } - QString osmWebsite, osmUser, osmPwd; - - osmWebsite = M_PREFS->getOsmApiUrl(); - osmUser = M_PREFS->getOsmUser(); - osmPwd = M_PREFS->getOsmPassword(); if (Main) Main->view()->setUpdatesEnabled(false); bool OK = true; - Downloader Rcv(osmUser, osmPwd); + Downloader Rcv(M_PREFS->getOsmServer()); for (int i=0; igetOsmServer(), theDocument, theLayer); } if (Main) @@ -508,76 +487,6 @@ bool downloadFeatures(MainWindow* Main, const QList& idList , Doc return OK; } -bool downloadMapdust(MainWindow* Main, const CoordBox& aBox, Document* theDocument, SpecialLayer* theLayer) -{ - QUrl url; - - url.setUrl(M_PREFS->getMapdustUrl()); - - if (Main) - Main->view()->setUpdatesEnabled(false); - - Downloader theDownloader("", ""); - - SpecialLayer* trackLayer = theLayer; - if (!trackLayer) { - trackLayer = new SpecialLayer(QApplication::translate("Downloader","MapDust"), Layer::MapDustLayer); - trackLayer->setUploadable(false); - theDocument->add(trackLayer); - } - - IProgressWindow* aProgressWindow = dynamic_cast(Main); - if (!aProgressWindow) - return false; - - QProgressDialog* dlg = aProgressWindow->getProgressDialog(); - dlg->setWindowTitle(QApplication::translate("Downloader","Parsing...")); - - QProgressBar* Bar = aProgressWindow->getProgressBar(); - Bar->setTextVisible(false); - Bar->setMaximum(11); - - QLabel* Lbl = aProgressWindow->getProgressLabel(); - Lbl->setText(QApplication::translate("Downloader","Parsing XML")); - - if (dlg) - dlg->show(); - - theDownloader.setAnimator(dlg,Lbl,Bar,true); - Lbl->setText(QApplication::translate("Downloader","Downloading points")); - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - QUrlQuery theQuery(url); -#define theQuery theQuery -#else -#define theQuery url -#endif - theQuery.addQueryItem("t", COORD2STRING(aBox.topRight().y())); - theQuery.addQueryItem("l", COORD2STRING(aBox.bottomLeft().x())); - theQuery.addQueryItem("b", COORD2STRING(aBox.bottomLeft().y())); - theQuery.addQueryItem("r", COORD2STRING(aBox.topRight().x())); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - url.setQuery(theQuery); -#endif -#undef theQuery - - if (!theDownloader.go(url)) - return false; - if (theDownloader.resultCode() != 200) - return false; - QByteArray Ar(theDownloader.content()); - ImportExportGdal gdal(theDocument); - bool OK = gdal.import(trackLayer, Ar, false); - - if (Main) - Main->view()->setUpdatesEnabled(true); - if (OK) { - if (Main) - Main->invalidateView(); - } - return OK; -} - bool downloadMoreOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocument) { Layer* theLayer; @@ -587,17 +496,12 @@ bool downloadMoreOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocum } else theLayer = (Layer*)theDocument->getLastDownloadLayer(); - QString osmWebsite, osmUser, osmPwd; - - osmWebsite = M_PREFS->getOsmApiUrl(); - osmUser = M_PREFS->getOsmUser(); - osmPwd = M_PREFS->getOsmPassword(); - qDebug() << "Requesting more from " << osmWebsite; + OsmServer server = M_PREFS->getOsmServer(); Main->view()->setUpdatesEnabled(false); bool OK = true; - OK = downloadOSM(Main,osmWebsite,osmUser,osmPwd,aBox,theDocument,theLayer); + OK = downloadOSM(Main,server,aBox,theDocument,theLayer); Main->view()->setUpdatesEnabled(true); if (OK) { @@ -623,9 +527,7 @@ bool downloadOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocument) QDialog * dlg = new QDialog(Main); - osmWebsite = M_PREFS->getOsmApiUrl(); - osmUser = M_PREFS->getOsmUser(); - osmPwd = M_PREFS->getOsmPassword(); + OsmServer server = M_PREFS->getOsmServer(); Ui::DownloadMapDialog ui; ui.setupUi(dlg); @@ -667,20 +569,18 @@ bool downloadOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocument) else if (ui.FromLink->isChecked()) { QString link = ui.Link->text(); - if (link.contains("/api/")) { + if (link.contains("/api/0.6/")) { directAPI=true; directUrl = link; } else if (link.contains("/browse/")) { QString tag("/browse/"); int ix = link.lastIndexOf(tag) + tag.length(); - directUrl = M_PREFS->getOsmApiUrl(); if (!directUrl.endsWith("/")) directUrl += "/"; directUrl += link.right(link.length() - ix); if (!directUrl.endsWith("/")) directUrl += "/"; directUrl += "full"; directAPI=true; } else if (link.startsWith("way") || link.startsWith("node") || link.startsWith("relation")) { - directUrl = M_PREFS->getOsmApiUrl(); if (!directUrl.endsWith("/")) directUrl += "/"; directUrl += link; directAPI=true; @@ -694,7 +594,6 @@ bool downloadOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocument) else if (ui.FromXapi->isChecked()) { directAPI = true; - directUrl = M_PREFS->getXapiUrl(); //if (!directUrl.endsWith("/")) directUrl += "/"; directUrl += ui.edXapiUrl->text(); } @@ -711,12 +610,12 @@ bool downloadOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocument) if (directAPI) { if (ui.FromXapi->isChecked()) theLayer->setUploadable(false); - OK = downloadOSM(Main,QUrl(QUrl::fromEncoded(directUrl.toLatin1())),osmUser,osmPwd,theDocument,theLayer); + OK = downloadOSM(Main,QUrl(QUrl::fromEncoded(directUrl.toLatin1())),server,theDocument,theLayer); } else - OK = downloadOSM(Main,osmWebsite,osmUser,osmPwd,Clip,theDocument,theLayer); + OK = downloadOSM(Main,server,Clip,theDocument,theLayer); if (OK && ui.IncludeTracks->isChecked()) - OK = downloadTracksFromOSM(Main,osmWebsite,osmUser,osmPwd, Clip,theDocument); + OK = downloadTracksFromOSM(Main,server, Clip,theDocument); Main->view()->setUpdatesEnabled(true); if (OK) { diff --git a/src/Sync/DownloadOSM.h b/src/Sync/DownloadOSM.h index 70a8081b..9cdc1d77 100644 --- a/src/Sync/DownloadOSM.h +++ b/src/Sync/DownloadOSM.h @@ -20,6 +20,7 @@ class SpecialLayer; #include #include #include +#include "Utils/OsmServer.h" #include "IFeature.h" @@ -28,7 +29,7 @@ class Downloader : public QObject Q_OBJECT public: - Downloader(const QString& aUser, const QString& aPwd); + Downloader(OsmServer server); bool request(const QString& theMethod, const QUrl& URL, const QString& Out); bool go(const QUrl& url); @@ -53,14 +54,10 @@ class Downloader : public QObject public slots: void progress( qint64 done, qint64 total ); - void on_requestFinished( QNetworkReply *reply); - void on_authenticationRequired( QNetworkReply *reply, QAuthenticator *auth); void animate(); - void on_Cancel_clicked(); private: - QNetworkAccessManager netManager; - QString User, Password; + OsmServer server; QByteArray Content; int Result; QString LocationText; @@ -80,7 +77,6 @@ bool downloadMoreOSM(MainWindow* Main, const CoordBox& aBox , Document* theDocum bool downloadFeatures(MainWindow* Main, const QList& aDownloadList , Document* theDocument); bool downloadFeature(MainWindow* Main, const IFeature::FId& id, Document* theDocument, Layer* theLayer=NULL); bool downloadFeatures(MainWindow* Main, const QList& aDownloadList, Document* theDocument, Layer* theLayer=NULL); -bool downloadMapdust(MainWindow* Main, const CoordBox& aBox, Document* theDocument, SpecialLayer* theLayer=NULL); bool checkForConflicts(Document* theDocument); diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 9fe17513..91380b84 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -15,6 +15,8 @@ class OsmServerImplBasic : public IOsmServerImpl { return m_info.Url; } + OsmServerInfo const getServerInfo() const { return m_info; } + virtual QNetworkReply* get(const QUrl &url) { return m_manager.get(QNetworkRequest(baseUrl().resolved(url))); } @@ -27,6 +29,10 @@ class OsmServerImplBasic : public IOsmServerImpl { return m_manager.deleteResource(QNetworkRequest(baseUrl().resolved(url))); } + virtual QNetworkReply* sendRequest(QNetworkRequest &request, const QByteArray &verb, const QByteArray &data) { + return m_manager.sendCustomRequest(request, verb, data); + } + virtual void authenticate() { QUrl base(m_info.Url); auto reply = m_manager.get(QNetworkRequest(base.resolved(QUrl("api/0.6/user/details")))); @@ -48,7 +54,7 @@ class OsmServerImplBasic : public IOsmServerImpl { void on_sslErrors(QNetworkReply *reply, const QList& errors); private: - OsmServerInfo& m_info; + OsmServerInfo m_info; QNetworkAccessManager& m_manager; }; @@ -65,6 +71,8 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { return m_info.Url; } + OsmServerInfo const getServerInfo() const { return m_info; } + virtual QNetworkReply* get(const QUrl &url) { return m_oauth2.get(baseUrl().resolved(url)); } @@ -76,18 +84,24 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { virtual QNetworkReply* deleteResource(const QUrl &url) { return m_oauth2.deleteResource(baseUrl().resolved(url)); } + + virtual QNetworkReply* sendRequest(QNetworkRequest &request, const QByteArray &verb, const QByteArray &data) { + qDebug() << "Sending request to URL " << request.url() << " with verb" << verb << "and data" << data; + m_oauth2.prepareRequest(&request, data); + return m_manager.sendCustomRequest(request, verb, data); + } virtual void authenticate(); private: - OsmServerInfo& m_info; + OsmServerInfo m_info; QNetworkAccessManager& m_manager; QOAuth2AuthorizationCodeFlow m_oauth2; QString generateCodeVerifier(); }; -std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager) { +std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager) { if (info.Type == OsmServerInfo::AuthType::Basic) { return std::make_shared(info, manager); } else if (info.Type == OsmServerInfo::AuthType::OAuth2) { diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index 8e58e8ff..30441e9d 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -49,34 +49,23 @@ class IOsmServerImpl : public QObject { virtual QNetworkReply* get(const QUrl &url) = 0; virtual QNetworkReply* put(const QUrl &url, const QByteArray &data) = 0; virtual QNetworkReply* deleteResource(const QUrl &url) = 0; + virtual QNetworkReply* sendRequest(QNetworkRequest &request, const QByteArray &verb, const QByteArray &data) = 0; virtual void authenticate() = 0; + + virtual OsmServerInfo const getServerInfo() const = 0; + + virtual QUrl baseUrl() const { + return QUrl(getServerInfo().Url); + } signals: void authenticated(); void failed(int error); }; -std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager); - -class OsmServer : public IOsmServerImpl { - Q_OBJECT - - public: - OsmServer(QNetworkAccessManager& manager); +Q_DECLARE_INTERFACE(IOsmServerImpl, "InterfaceIOsmServerImpl") - void setInfo(OsmServerInfo& info) { - m_info = info; - m_impl = makeOsmServer(m_info, m_manager); - } +using OsmServer = std::shared_ptr; - virtual QNetworkReply* get(const QUrl &url); - virtual QNetworkReply* put(const QUrl &url, const QByteArray &data); - virtual QNetworkReply* deleteResource(const QUrl &url); - virtual void authenticate(); - - private: - OsmServerInfo& m_info; - QNetworkAccessManager& m_manager; - std::shared_ptr m_impl; -}; +OsmServer makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager); #endif diff --git a/src/common/GotoDialog.cpp b/src/common/GotoDialog.cpp index 7fd03d7b..8fe9f0dd 100644 --- a/src/common/GotoDialog.cpp +++ b/src/common/GotoDialog.cpp @@ -69,7 +69,7 @@ GotoDialog::GotoDialog(MapView* aView, QWidget *parent) .arg(QString::number(OsmZoom)) ); coordOsmApi->setText( QString("%1/map?bbox=%2,%3,%4,%5") - .arg(M_PREFS->getOsmApiUrl()) +// .arg(M_PREFS->getOsmApiUrl()) //FIXME: Wut? .arg(COORD2STRING(theViewport.bottomLeft().x())) .arg(COORD2STRING(theViewport.bottomLeft().y())) .arg(COORD2STRING(theViewport.topRight().x())) From dde48f0427d23280b5c25e9deacd8ad7a7bd5c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Fri, 5 Jul 2024 22:50:07 +0200 Subject: [PATCH 05/30] Fixed integration errors. --- CMakeLists.txt | 2 - src/Preferences/PreferencesDialog.cpp | 41 +++++---- src/Sync/DownloadOSM.cpp | 6 ++ src/Sync/DownloadOSM.h | 1 + src/Utils/HttpAuth.cpp | 114 -------------------------- src/Utils/HttpAuth.h | 36 -------- src/Utils/OsmServer.cpp | 31 ++++++- 7 files changed, 63 insertions(+), 168 deletions(-) delete mode 100644 src/Utils/HttpAuth.cpp delete mode 100644 src/Utils/HttpAuth.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dda0d34..04dbd792 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,8 +257,6 @@ src/Layers/LayerWidget.h src/Layers/LayerWidget.ui src/Layers/FilterEditDialog.ui src/Layers/LayerPrivate.h -src/Utils/HttpAuth.h -src/Utils/HttpAuth.cpp src/Utils/ShortcutOverrideFilter.h src/Utils/SelectionDialog.cpp src/Utils/ProjectionChooser.h diff --git a/src/Preferences/PreferencesDialog.cpp b/src/Preferences/PreferencesDialog.cpp index 86d23770..d4cce24f 100644 --- a/src/Preferences/PreferencesDialog.cpp +++ b/src/Preferences/PreferencesDialog.cpp @@ -47,13 +47,17 @@ class OsmServerWidget : public QWidget, public Ui::OsmServerWidget public: OsmServerWidget(QWidget * parent = 0, Qt::WindowFlags f = Qt::Widget); + OsmServerInfo getOsmServerInfo() const; + QNetworkAccessManager* m_nm; /* TODO: Inject. */ + public slots: void on_tbOsmServerAdd_clicked(); void on_tbOsmServerDel_clicked(); void on_rbOsmServerSelected_clicked(); void on_tbOAuth2Login_clicked(); - void on_httpAuth_authenticated(); - void on_httpAuth_failed(); + void on_httpAuthAuthenticated(); + void on_httpAuthFailed(); + private: std::shared_ptr m_impl; }; @@ -65,9 +69,7 @@ OsmServerWidget::OsmServerWidget(QWidget * parent, Qt::WindowFlags f) { setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - - connect(&*m_impl, &IOsmServerImpl::authenticated, this, &OsmServerWidget::on_httpAuth_authenticated); - connect(&*m_impl, &IOsmServerImpl::failed, this, &OsmServerWidget::on_httpAuth_failed); + m_nm = new QNetworkAccessManager(this); } void OsmServerWidget::on_tbOsmServerAdd_clicked() @@ -109,6 +111,16 @@ void OsmServerWidget::on_tbOsmServerDel_clicked() } } +OsmServerInfo OsmServerWidget::getOsmServerInfo() const { + OsmServerInfo srv; + srv.Url = edOsmServerUrl->text(); + srv.Type = static_cast(authType->currentIndex()); + srv.User = edOsmServerUser->text(); + srv.Password = edOsmServerPwd->text(); + srv.Selected = rbOsmServerSelected->isChecked(); + return srv; +} + void OsmServerWidget::on_rbOsmServerSelected_clicked() { QLayout* lay = parentWidget()->layout(); @@ -126,19 +138,25 @@ void OsmServerWidget::on_rbOsmServerSelected_clicked() void OsmServerWidget::on_tbOAuth2Login_clicked() { qDebug() << "Start login..."; - //httpAuth.setBaseUrl(QUrl(this->edOsmServerUrl->text()).resolved(QUrl("/"))); + + OsmServerInfo srv = getOsmServerInfo(); + m_impl = makeOsmServer(srv, *m_nm); + + connect(&*m_impl, &IOsmServerImpl::authenticated, this, &OsmServerWidget::on_httpAuthAuthenticated); + connect(&*m_impl, &IOsmServerImpl::failed, this, &OsmServerWidget::on_httpAuthFailed); m_impl->authenticate(); + this->lbLoginState->setText(tr("Authenticating")); this->tbOAuth2Login->setEnabled(false); } -void OsmServerWidget::on_httpAuth_authenticated() { +void OsmServerWidget::on_httpAuthAuthenticated() { qDebug() << "Authenticated!"; this->lbLoginState->setText(tr("Authenticated")); this->tbOAuth2Login->setEnabled(true); } -void OsmServerWidget::on_httpAuth_failed() { +void OsmServerWidget::on_httpAuthFailed() { qDebug() << "Failed!"; this->lbLoginState->setText(tr("Failed")); this->tbOAuth2Login->setEnabled(true); @@ -401,12 +419,7 @@ void PreferencesDialog::savePrefs() if (!wOsmServer) continue; - OsmServerInfo srv; - srv.Url = wOsmServer->edOsmServerUrl->text(); - srv.Type = static_cast(wOsmServer->authType->currentIndex()); - srv.User = wOsmServer->edOsmServerUser->text(); - srv.Password = wOsmServer->edOsmServerPwd->text(); - srv.Selected = wOsmServer->rbOsmServerSelected->isChecked(); + OsmServerInfo srv = wOsmServer->getOsmServerInfo(); if (srv.Selected) { M_PREFS->setOsmServerIndex(i); diff --git a/src/Sync/DownloadOSM.cpp b/src/Sync/DownloadOSM.cpp index 4cf8c126..00257c2b 100644 --- a/src/Sync/DownloadOSM.cpp +++ b/src/Sync/DownloadOSM.cpp @@ -65,6 +65,12 @@ void Downloader::setAnimator(QProgressDialog *anAnimator, QLabel* anAnimatorLabe } } +void Downloader::on_Cancel_clicked() +{ + Error = true; + if (Loop.isRunning()) + Loop.exit(QDialog::Rejected); +} bool Downloader::go(const QUrl& url) { return request("GET", url, QString()); diff --git a/src/Sync/DownloadOSM.h b/src/Sync/DownloadOSM.h index 9cdc1d77..bf156ad7 100644 --- a/src/Sync/DownloadOSM.h +++ b/src/Sync/DownloadOSM.h @@ -55,6 +55,7 @@ class Downloader : public QObject public slots: void progress( qint64 done, qint64 total ); void animate(); + void on_Cancel_clicked(); private: OsmServer server; diff --git a/src/Utils/HttpAuth.cpp b/src/Utils/HttpAuth.cpp deleted file mode 100644 index 85b970f2..00000000 --- a/src/Utils/HttpAuth.cpp +++ /dev/null @@ -1,114 +0,0 @@ - -#include "HttpAuth.h" -//#include -#include -#include -#include -#include - - -//FIXME: The client ID and secret are not really a secret, since there is no -//way to hide them in a desktop application. In case of abuse, the client ID -//can be revoked by osm.org. It might be a good idea to make it configurable, -//so that the user can enter their own client ID and secret in this case. -// -//Client ID wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps -//Client Secret evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM // FIXME: The secret is not needed. Why? -//Make sure to save this secret - it will not be accessible again -//Permissions -// -// Read user preferences (read_prefs) -// Modify user preferences (write_prefs) -// Create diary entries, comments and make friends (write_diary) -// Modify the map (write_api) -// Read private GPS traces (read_gpx) -// Upload GPS traces (write_gpx) -// Modify notes (write_notes) -// -//Redirect URIs -// -// http://127.0.0.1:1337/ - -#include - -QString generateCodeVerifier() { - std::random_device rd; - std::mt19937 gen(rd()); - int bytes = 32; - std::uniform_int_distribution dis(0, 255); - QString result; - for (int i = 0; i < bytes; i++) { - result.append(QString::number(dis(gen), 16)); - } - return result; -} - -HttpAuth::HttpAuth(QObject *parent) : QObject(parent) { - oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); - oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); -} - -void HttpAuth::setBaseUrl(const QUrl url) { - QUrl base(url); - oauth2.setAuthorizationUrl(base.resolved(QUrl("oauth2/authorize"))); - oauth2.setAccessTokenUrl(base.resolved(QUrl("oauth2/token"))); - qDebug() << "Base URL: " << base; - qDebug() << "Authorization URL: " << oauth2.authorizationUrl(); - qDebug() << "Access Token URL: " << oauth2.accessTokenUrl(); -} - -void HttpAuth::Login() { - auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); - qDebug() << "connect."; - oauth2.setReplyHandler(replyHandler); - qDebug() << "connect2."; - codeVerifier = generateCodeVerifier(); - codeChallenge = QString(QCryptographicHash::hash(codeVerifier.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); - - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=]( - QAbstractOAuth::Status status) { - qDebug() << "OAuth status changed to " << int(status); - if (status == QAbstractOAuth::Status::Granted) { - qDebug() << "Granted, token: " << oauth2.token(); - emit authenticated(); - auto reply = oauth2.get(QUrl("https://api.openstreetmap.org/api/0.6/user/details")); - connect(reply, &QNetworkReply::finished, [=]() { - reply->deleteLater(); - - if (reply->error() != QNetworkReply::NoError) { - qCritical() << "Error:" << reply->errorString(); - return; - } - - qDebug() << reply->readAll(); - }); - } else if (status == QAbstractOAuth::Status::TemporaryCredentialsReceived) { - qDebug() << "Temporary credentials."; - } else { - qWarning() << "Status is not granted: " << int(status); - emit failed(int(status)); - } - }); - oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap*params){ - qDebug() << "Stage: " << int(stage) << "with params" << *params; - if (params) { - switch (stage) { - case QAbstractOAuth::Stage::RequestingAuthorization: - qDebug() << "Requesting Authorization."; - params->insert("code_challenge", codeChallenge); - params->insert("code_challenge_method", "S256"); - break; - case QAbstractOAuth::Stage::RequestingAccessToken: - qDebug() << "Requesting Access Token."; - params->insert("code_verifier", codeVerifier); - break; - default: - qDebug() << "default stage."; - } - } - qDebug() << "Stage: " << int(stage) << "with params" << *params; - }); - - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, - &QDesktopServices::openUrl); -} diff --git a/src/Utils/HttpAuth.h b/src/Utils/HttpAuth.h deleted file mode 100644 index dcbd6b27..00000000 --- a/src/Utils/HttpAuth.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef __HTTPAUTH_H__ -#define __HTTPAUTH_H__ - -#include -#include - -#include - -class HttpAuth : public QObject -{ - Q_OBJECT - -public: - HttpAuth(QObject *parent = nullptr); - void Login(); - - void setBaseUrl(const QUrl url); - - QString token() const { return oauth2.token(); } - -public slots: - void grant() { oauth2.grant(); }; - -signals: - void authenticated(); - void failed(int error); - -private: - QOAuth2AuthorizationCodeFlow oauth2; - bool permanent = false; - QString codeVerifier; - QString codeChallenge; -}; - - -#endif diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 91380b84..d8b892c7 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -1,9 +1,33 @@ #include #include +#include #include #include "OsmServer.h" -#include "HttpAuth.h" + +#include +// +//FIXME: The client ID and secret are not really a secret, since there is no +//way to hide them in a desktop application. In case of abuse, the client ID +//can be revoked by osm.org. It might be a good idea to make it configurable, +//so that the user can enter their own client ID and secret in this case. +// +//Client ID wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps +//Client Secret evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM // FIXME: The secret is not needed. Why? +//Make sure to save this secret - it will not be accessible again +//Permissions +// +// Read user preferences (read_prefs) +// Modify user preferences (write_prefs) +// Create diary entries, comments and make friends (write_diary) +// Modify the map (write_api) +// Read private GPS traces (read_gpx) +// Upload GPS traces (write_gpx) +// Modify notes (write_notes) +// +//Redirect URIs +// +// http://127.0.0.1:1337/ class OsmServerImplBasic : public IOsmServerImpl { public: @@ -65,6 +89,7 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { { m_oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); + m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); } QUrl baseUrl() const { @@ -161,7 +186,7 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Authorization URL: " << m_oauth2.authorizationUrl(); qDebug() << "Access Token URL: " << m_oauth2.accessTokenUrl(); - auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); + auto replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress("127.0.0.1"), 1337, this); qDebug() << "connect."; m_oauth2.setReplyHandler(replyHandler); qDebug() << "connect2."; @@ -215,4 +240,6 @@ void OsmServerImplOAuth2::authenticate() { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); + + m_oauth2.grant(); } From 889c43ef485ad9b66904699a561f83428bef3917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Fri, 5 Jul 2024 23:14:49 +0200 Subject: [PATCH 06/30] Fixed oauth redirect issues. --- src/Utils/OsmServer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index d8b892c7..46e7dc64 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -218,14 +218,15 @@ void OsmServerImplOAuth2::authenticate() { emit failed(int(status)); } }); - m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap*params){ - qDebug() << "Stage: " << int(stage) << "with params" << *params; + m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, QMultiMap*params){ if (params) { + qDebug() << "Stage: " << int(stage) << "with params" << *params; switch (stage) { case QAbstractOAuth::Stage::RequestingAuthorization: qDebug() << "Requesting Authorization."; params->insert("code_challenge", codeChallenge); params->insert("code_challenge_method", "S256"); + params->replace("redirect_uri", "http://127.0.0.1:1337/"); break; case QAbstractOAuth::Stage::RequestingAccessToken: qDebug() << "Requesting Access Token."; @@ -234,8 +235,8 @@ void OsmServerImplOAuth2::authenticate() { default: qDebug() << "default stage."; } + qDebug() << "Stage: " << int(stage) << "with params" << *params; } - qDebug() << "Stage: " << int(stage) << "with params" << *params; }); connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, From d03c7a1712367e3c75bf116975e5014d953f0593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 17:48:11 +0200 Subject: [PATCH 07/30] Removed PKCE temporarily, needs more debugging. --- src/Utils/OsmServer.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 46e7dc64..5596a032 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -88,6 +88,7 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { : m_info(info), m_manager(manager),m_oauth2(&manager) { m_oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); + //m_oauth2.setScope("read_prefs%20write_prefs%20write_api%20read_gpx%20write_gpx%20write_notes"); m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); } @@ -205,7 +206,7 @@ void OsmServerImplOAuth2::authenticate() { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - qCritical() << "Error:" << reply->errorString(); + qCritical() << "Error:" << reply->error() << reply->errorString(); return; } @@ -218,19 +219,21 @@ void OsmServerImplOAuth2::authenticate() { emit failed(int(status)); } }); + m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, QMultiMap*params){ if (params) { qDebug() << "Stage: " << int(stage) << "with params" << *params; switch (stage) { case QAbstractOAuth::Stage::RequestingAuthorization: qDebug() << "Requesting Authorization."; - params->insert("code_challenge", codeChallenge); - params->insert("code_challenge_method", "S256"); + //params->insert("code_challenge", codeChallenge); + //params->insert("code_challenge_method", "S256"); params->replace("redirect_uri", "http://127.0.0.1:1337/"); break; case QAbstractOAuth::Stage::RequestingAccessToken: qDebug() << "Requesting Access Token."; - params->insert("code_verifier", codeVerifier); + //params->insert("code_verifier", codeVerifier); + params->replace("redirect_uri", "http://127.0.0.1:1337/"); break; default: qDebug() << "default stage."; From ce777d84a23a3e53a952149561a05231493acfdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 18:10:05 +0200 Subject: [PATCH 08/30] Fixed user details URL. --- src/Utils/OsmServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 5596a032..9a54af12 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -201,7 +201,7 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Granted, token: " << m_oauth2.token(); m_info.Password = m_oauth2.token(); emit authenticated(); - auto reply = get(QUrl("user/details")); + auto reply = get(QUrl("/api/0.6/user/details")); connect(reply, &QNetworkReply::finished, [=]() { reply->deleteLater(); From 849fbfb1a8a22a26b4dc7ef566484bdb8ec5f426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 18:11:34 +0200 Subject: [PATCH 09/30] Added url logging. --- src/Utils/OsmServer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 9a54af12..5074e222 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -100,19 +100,22 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { OsmServerInfo const getServerInfo() const { return m_info; } virtual QNetworkReply* get(const QUrl &url) { + qDebug() << "get: " << baseUrl().resolved(url); return m_oauth2.get(baseUrl().resolved(url)); } virtual QNetworkReply* put(const QUrl &url, const QByteArray &data) { + qDebug() << "put: " << baseUrl().resolved(url); return m_oauth2.put(baseUrl().resolved(url), data); } virtual QNetworkReply* deleteResource(const QUrl &url) { + qDebug() << "delete: " << baseUrl().resolved(url); return m_oauth2.deleteResource(baseUrl().resolved(url)); } virtual QNetworkReply* sendRequest(QNetworkRequest &request, const QByteArray &verb, const QByteArray &data) { - qDebug() << "Sending request to URL " << request.url() << " with verb" << verb << "and data" << data; + qDebug() << "custom request" << request.url() << " with verb" << verb << "and data" << data; m_oauth2.prepareRequest(&request, data); return m_manager.sendCustomRequest(request, verb, data); } From f81b0bc26056994ee3daadc20d8d2eb0c0ce680b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 19:13:27 +0200 Subject: [PATCH 10/30] Fixed token storage. --- src/Preferences/PreferencesDialog.cpp | 22 +++++++++++----------- src/Utils/OsmServer.cpp | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Preferences/PreferencesDialog.cpp b/src/Preferences/PreferencesDialog.cpp index d4cce24f..07240568 100644 --- a/src/Preferences/PreferencesDialog.cpp +++ b/src/Preferences/PreferencesDialog.cpp @@ -48,6 +48,7 @@ class OsmServerWidget : public QWidget, public Ui::OsmServerWidget OsmServerWidget(QWidget * parent = 0, Qt::WindowFlags f = Qt::Widget); OsmServerInfo getOsmServerInfo() const; + void setOsmServerInfo(OsmServerInfo srv); QNetworkAccessManager* m_nm; /* TODO: Inject. */ public slots: @@ -121,6 +122,14 @@ OsmServerInfo OsmServerWidget::getOsmServerInfo() const { return srv; } +void OsmServerWidget::setOsmServerInfo(OsmServerInfo srv) { + edOsmServerUrl->setText(srv.Url); + authType->setCurrentIndex(static_cast(srv.Type)); + edOsmServerUser->setText(srv.User); + edOsmServerPwd->setText(srv.Password); + rbOsmServerSelected->setChecked(srv.Selected); +} + void OsmServerWidget::on_rbOsmServerSelected_clicked() { QLayout* lay = parentWidget()->layout(); @@ -152,6 +161,7 @@ void OsmServerWidget::on_tbOAuth2Login_clicked() { void OsmServerWidget::on_httpAuthAuthenticated() { qDebug() << "Authenticated!"; + setOsmServerInfo(m_impl->getServerInfo()); this->lbLoginState->setText(tr("Authenticated")); this->tbOAuth2Login->setEnabled(true); } @@ -253,10 +263,6 @@ void PreferencesDialog::loadPrefs() if (!theOsmServers->size()) { OsmServerWidget* wOSmServer = new OsmServerWidget(grpOSM); - wOSmServer->edOsmServerUrl->setText(""); - // wOSmServer->authType->setCurrentIndex(0); // Keep default authType per UI. - wOSmServer->edOsmServerUser->setText(""); - wOSmServer->edOsmServerPwd->setText(""); wOSmServer->rbOsmServerSelected->setChecked(true); wOSmServer->tbOsmServerDel->setEnabled(false); @@ -264,13 +270,7 @@ void PreferencesDialog::loadPrefs() } else { foreach(OsmServerInfo srv, *theOsmServers) { OsmServerWidget* wOSmServer = new OsmServerWidget(grpOSM); - - wOSmServer->edOsmServerUrl->setText(srv.Url); - wOSmServer->authType->setCurrentIndex(static_cast(srv.Type)); - wOSmServer->edOsmServerUser->setText(srv.User); - wOSmServer->edOsmServerPwd->setText(srv.Password); - wOSmServer->rbOsmServerSelected->setChecked(srv.Selected); - + wOSmServer->setOsmServerInfo(srv); OsmServersLayout->addWidget(wOSmServer); } } diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 5074e222..c7e397a5 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -89,8 +89,18 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { { m_oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); //m_oauth2.setScope("read_prefs%20write_prefs%20write_api%20read_gpx%20write_gpx%20write_notes"); - m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); - m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); + QString host = QUrl(m_info.Url).host(); + if (host == "master.apis.dev.openstreetmap.org") { + m_oauth2.setClientIdentifier("Ydpjx_nAy3fnGN5X1LiFwqToueYvwV0Nl1_IUUi7H3I"); + m_oauth2.setClientIdentifierSharedKey("SuWJYZr9hjCmZH4PaMrvB48aVR_vgIw4D2VUEKPlR4c"); + } else { + if (host != "openstreetmap.org") { + qWarning() << "Unknown OSM API host " << host << ", assuming alias to openstreetmap.org"; + } + m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); + m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); + } + m_oauth2.setToken(m_info.Password); } QUrl baseUrl() const { From 8c38f77d0618064d01676aa51181317dcbd605ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 19:44:06 +0200 Subject: [PATCH 11/30] Added networkauth to CI. --- .github/workflows/build.yml | 4 ++-- ci/travis-linux-install.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1266f2e..309c0d3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,7 +102,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update -qq - sudo apt-get -y install gdb libgdal-dev libproj-dev qt6-base-dev qt6-base-dev-tools qt6-tools-dev build-essential libgl1-mesa-dev cmake git libexiv2-dev libqt6svg* libqt6core5compat* qt6-l10n-tools qt6-tools-dev-tools protobuf-compiler + sudo apt-get -y install gdb libgdal-dev libproj-dev qt6-base-dev qt6-base-dev-tools qt6-tools-dev build-essential libgl1-mesa-dev cmake git libexiv2-dev libqt6svg* libqt6networkauth* libqt6core5compat* qt6-l10n-tools qt6-tools-dev-tools protobuf-compiler - name: Build run: | ./ci/travis-linux-script.sh @@ -117,7 +117,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update -qq - sudo apt-get -y install gdb libgdal-dev libproj-dev qt6-base-dev qt6-base-dev-tools qt6-tools-dev build-essential libgl1-mesa-dev cmake git libexiv2-dev libqt6svg* libqt6core5compat* qt6-l10n-tools qt6-tools-dev-tools protobuf-compiler + sudo apt-get -y install gdb libgdal-dev libproj-dev qt6-base-dev qt6-base-dev-tools qt6-tools-dev build-essential libgl1-mesa-dev cmake git libexiv2-dev libqt6svg* libqt6networkauth* libqt6core5compat* qt6-l10n-tools qt6-tools-dev-tools protobuf-compiler sudo apt-get -y install python3-pip libglib2.0-dev bash dash squashfs-tools zsync fakeroot sudo pip install git+https://github.com/AppImageCrafters/appimage-builder.git - name: Build diff --git a/ci/travis-linux-install.sh b/ci/travis-linux-install.sh index ec64ced8..7dc60772 100755 --- a/ci/travis-linux-install.sh +++ b/ci/travis-linux-install.sh @@ -9,4 +9,4 @@ sudo echo "deb http://archive.ubuntu.com/ubuntu ${TRAVIS_DIST} main universe res sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' sudo apt-add-repository -y ${QT_REPO} sudo apt-get update -qq -sudo apt-get -y install gdb libgdal-dev libproj-dev qt${QT_PREFIX}base qt${QT_PREFIX}tools qt${QT_PREFIX}svg build-essential libgl1-mesa-dev cmake git libexiv2-dev protobuf-compiler +sudo apt-get -y install gdb libgdal-dev libproj-dev qt${QT_PREFIX}base qt${QT_PREFIX}tools qt${QT_PREFIX}svg qt${QT_PREFIX}networkauth build-essential libgl1-mesa-dev cmake git libexiv2-dev protobuf-compiler From fde74cccb405ec639c0946a262e73874fff1f00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 21:10:36 +0200 Subject: [PATCH 12/30] Added networkauth to appimage, fixed lib name. --- ci/travis-linux-install.sh | 2 +- cmake/AppImageBuilder.yml.in | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/travis-linux-install.sh b/ci/travis-linux-install.sh index 7dc60772..256d5a23 100755 --- a/ci/travis-linux-install.sh +++ b/ci/travis-linux-install.sh @@ -9,4 +9,4 @@ sudo echo "deb http://archive.ubuntu.com/ubuntu ${TRAVIS_DIST} main universe res sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' sudo apt-add-repository -y ${QT_REPO} sudo apt-get update -qq -sudo apt-get -y install gdb libgdal-dev libproj-dev qt${QT_PREFIX}base qt${QT_PREFIX}tools qt${QT_PREFIX}svg qt${QT_PREFIX}networkauth build-essential libgl1-mesa-dev cmake git libexiv2-dev protobuf-compiler +sudo apt-get -y install gdb libgdal-dev libproj-dev qt${QT_PREFIX}base qt${QT_PREFIX}tools qt${QT_PREFIX}svg qt${QT_PREFIX}networkauth* build-essential libgl1-mesa-dev cmake git libexiv2-dev protobuf-compiler diff --git a/cmake/AppImageBuilder.yml.in b/cmake/AppImageBuilder.yml.in index 334136a5..9f37e69d 100644 --- a/cmake/AppImageBuilder.yml.in +++ b/cmake/AppImageBuilder.yml.in @@ -30,6 +30,7 @@ AppDir: - libqt6dbus6 - libqt6svg6 - libqt6core5compat6 + - libqt6networkauth6 - qt6-qpa-plugins files: @@ -49,4 +50,5 @@ AppDir: AppImage: update-information: None sign-key: None + comp: xz arch: x86_64 From 7edb6f8171dcc28cf4e1ea665c4fe4ab6f8e8e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 21:12:44 +0200 Subject: [PATCH 13/30] Replaced qt5 wildcard with specific version. --- ci/travis-linux-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/travis-linux-install.sh b/ci/travis-linux-install.sh index 256d5a23..9cca9fa8 100755 --- a/ci/travis-linux-install.sh +++ b/ci/travis-linux-install.sh @@ -9,4 +9,4 @@ sudo echo "deb http://archive.ubuntu.com/ubuntu ${TRAVIS_DIST} main universe res sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main' sudo apt-add-repository -y ${QT_REPO} sudo apt-get update -qq -sudo apt-get -y install gdb libgdal-dev libproj-dev qt${QT_PREFIX}base qt${QT_PREFIX}tools qt${QT_PREFIX}svg qt${QT_PREFIX}networkauth* build-essential libgl1-mesa-dev cmake git libexiv2-dev protobuf-compiler +sudo apt-get -y install gdb libgdal-dev libproj-dev qt${QT_PREFIX}base qt${QT_PREFIX}tools qt${QT_PREFIX}svg qt${QT_PREFIX}networkauth-no-lgpl build-essential libgl1-mesa-dev cmake git libexiv2-dev protobuf-compiler From 101db9138b590969dd689fec19c604c07acb73af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 22:11:26 +0200 Subject: [PATCH 14/30] Fixed compilation errors on Qt5. --- src/Utils/OsmServer.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index c7e397a5..f004513c 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -233,7 +233,8 @@ void OsmServerImplOAuth2::authenticate() { } }); - m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, QMultiMap*params){ + m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, auto*params){ + /* Note: params is QMap for Qt5 and QMultiMap for Qt6 */ if (params) { qDebug() << "Stage: " << int(stage) << "with params" << *params; switch (stage) { @@ -241,12 +242,16 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Requesting Authorization."; //params->insert("code_challenge", codeChallenge); //params->insert("code_challenge_method", "S256"); - params->replace("redirect_uri", "http://127.0.0.1:1337/"); + // FIXME: Can be replaced by ->replace when Qt 6.x is minimum supported version + params->remove("redirect_uri"); + params->insert("redirect_uri", "http://127.0.0.1:1337/"); break; case QAbstractOAuth::Stage::RequestingAccessToken: qDebug() << "Requesting Access Token."; //params->insert("code_verifier", codeVerifier); - params->replace("redirect_uri", "http://127.0.0.1:1337/"); + //Note: technically, we no longer redirect, but OSM server rejects the token request unless we provide a matching redirect_uri + params->remove("redirect_uri"); + params->insert("redirect_uri", "http://127.0.0.1:1337/"); break; default: qDebug() << "default stage."; From 31e0b1a671a4cf807b305b0ad0321e28413c3344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 22:40:17 +0200 Subject: [PATCH 15/30] Re-enabled PKCE. Seems to be working now? --- src/Utils/OsmServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index f004513c..db3f9c8b 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -240,15 +240,15 @@ void OsmServerImplOAuth2::authenticate() { switch (stage) { case QAbstractOAuth::Stage::RequestingAuthorization: qDebug() << "Requesting Authorization."; - //params->insert("code_challenge", codeChallenge); - //params->insert("code_challenge_method", "S256"); + params->insert("code_challenge", codeChallenge); + params->insert("code_challenge_method", "S256"); // FIXME: Can be replaced by ->replace when Qt 6.x is minimum supported version params->remove("redirect_uri"); params->insert("redirect_uri", "http://127.0.0.1:1337/"); break; case QAbstractOAuth::Stage::RequestingAccessToken: qDebug() << "Requesting Access Token."; - //params->insert("code_verifier", codeVerifier); + params->insert("code_verifier", codeVerifier); //Note: technically, we no longer redirect, but OSM server rejects the token request unless we provide a matching redirect_uri params->remove("redirect_uri"); params->insert("redirect_uri", "http://127.0.0.1:1337/"); From 77a05ebcf16dee5dfa6592c5b73dc6d883027c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 6 Jul 2024 22:56:12 +0200 Subject: [PATCH 16/30] Added reply handler timeout and cleanup. --- src/Utils/OsmServer.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index db3f9c8b..7257e011 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "OsmServer.h" @@ -201,9 +202,13 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Access Token URL: " << m_oauth2.accessTokenUrl(); auto replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress("127.0.0.1"), 1337, this); - qDebug() << "connect."; + QTimer::singleShot(6000, replyHandler, [=]() { + qWarning() << "OAuth2 reply handler timed out."; + replyHandler->deleteLater(); + emit failed(QNetworkReply::TimeoutError); + }); + m_oauth2.setReplyHandler(replyHandler); - qDebug() << "connect2."; QString codeVerifier = generateCodeVerifier(); QString codeChallenge = QString(QCryptographicHash::hash(codeVerifier.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); @@ -214,6 +219,7 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Granted, token: " << m_oauth2.token(); m_info.Password = m_oauth2.token(); emit authenticated(); + replyHandler->deleteLater(); auto reply = get(QUrl("/api/0.6/user/details")); connect(reply, &QNetworkReply::finished, [=]() { reply->deleteLater(); @@ -229,6 +235,7 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Temporary credentials."; } else { qWarning() << "Status is not granted: " << int(status); + replyHandler->deleteLater(); emit failed(int(status)); } }); From f0ebe82bcac03fc184431b5b07554d546b34061c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sun, 14 Jul 2024 22:03:41 +0200 Subject: [PATCH 17/30] Improved error reporting. --- src/Utils/OsmServer.cpp | 63 ++++++++++++++++++++++++----------------- src/Utils/OsmServer.h | 10 ++++++- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 7257e011..de40d4b8 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -66,7 +66,7 @@ class OsmServerImplBasic : public IOsmServerImpl { if (reply->error() != QNetworkReply::NoError) { qCritical() << "Error:" << reply->errorString(); - emit failed(reply->error()); + emit failed(Error::Unauthorized, reply->errorString()); } else { qDebug() << reply->readAll(); emit authenticated(); @@ -85,24 +85,7 @@ class OsmServerImplBasic : public IOsmServerImpl { class OsmServerImplOAuth2 : public IOsmServerImpl { public: - OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessManager& manager) - : m_info(info), m_manager(manager),m_oauth2(&manager) - { - m_oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); - //m_oauth2.setScope("read_prefs%20write_prefs%20write_api%20read_gpx%20write_gpx%20write_notes"); - QString host = QUrl(m_info.Url).host(); - if (host == "master.apis.dev.openstreetmap.org") { - m_oauth2.setClientIdentifier("Ydpjx_nAy3fnGN5X1LiFwqToueYvwV0Nl1_IUUi7H3I"); - m_oauth2.setClientIdentifierSharedKey("SuWJYZr9hjCmZH4PaMrvB48aVR_vgIw4D2VUEKPlR4c"); - } else { - if (host != "openstreetmap.org") { - qWarning() << "Unknown OSM API host " << host << ", assuming alias to openstreetmap.org"; - } - m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); - m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); - } - m_oauth2.setToken(m_info.Password); - } + OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessManager& manager); QUrl baseUrl() const { return m_info.Url; @@ -194,6 +177,27 @@ QString OsmServerImplOAuth2::generateCodeVerifier() { return result; } +OsmServerImplOAuth2::OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessManager& manager) + : m_info(info), m_manager(manager),m_oauth2(&manager) +{ + m_oauth2.setScope("read_prefs write_prefs write_api read_gpx write_gpx write_notes"); + //m_oauth2.setScope("read_prefs%20write_prefs%20write_api%20read_gpx%20write_gpx%20write_notes"); + QString host = QUrl(m_info.Url).host(); + if (host == "master.apis.dev.openstreetmap.org") { + m_oauth2.setClientIdentifier("Ydpjx_nAy3fnGN5X1LiFwqToueYvwV0Nl1_IUUi7H3I"); + m_oauth2.setClientIdentifierSharedKey("SuWJYZr9hjCmZH4PaMrvB48aVR_vgIw4D2VUEKPlR4c"); + } else { + if (host != "openstreetmap.org") { + qWarning() << "Unknown OSM API host " << host << ", assuming alias to openstreetmap.org"; + } + m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); + m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); + } + m_oauth2.setToken(m_info.Password); + + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); +} + void OsmServerImplOAuth2::authenticate() { m_oauth2.setAuthorizationUrl(baseUrl().resolved(QUrl("oauth2/authorize"))); m_oauth2.setAccessTokenUrl(baseUrl().resolved(QUrl("oauth2/token"))); @@ -202,10 +206,10 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Access Token URL: " << m_oauth2.accessTokenUrl(); auto replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress("127.0.0.1"), 1337, this); - QTimer::singleShot(6000, replyHandler, [=]() { + QTimer::singleShot(60000, replyHandler, [=]() { qWarning() << "OAuth2 reply handler timed out."; replyHandler->deleteLater(); - emit failed(QNetworkReply::TimeoutError); + emit failed(Error::Timeout, tr("OAuth2 reply handler timed out.")); }); m_oauth2.setReplyHandler(replyHandler); @@ -235,11 +239,21 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Temporary credentials."; } else { qWarning() << "Status is not granted: " << int(status); - replyHandler->deleteLater(); - emit failed(int(status)); } }); + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::error, replyHandler, [this, replyHandler]() { + qDebug() << "QOAuth2AuthorizationCodeFlow::error raised."; + replyHandler->deleteLater(); + emit failed(Error::ReplyError, tr("QOAuth2AuthorizationCodeFlow::error raised.")); + }); + + connect(replyHandler, &QOAuthHttpServerReplyHandler::tokenRequestErrorOccurred, this, [this, replyHandler](QAbstractOAuth::Error error, const QString& errorString) { + qDebug() << "Token request error occurred: " << int(error) << errorString; + replyHandler->deleteLater(); + emit failed(Error::TokenRequestError, tr("Token request failed.") + "\n" + errorString); + }); + m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, auto*params){ /* Note: params is QMap for Qt5 and QMultiMap for Qt6 */ if (params) { @@ -267,8 +281,5 @@ void OsmServerImplOAuth2::authenticate() { } }); - connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, - &QDesktopServices::openUrl); - m_oauth2.grant(); } diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index 30441e9d..f8eb0c3b 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -57,9 +57,17 @@ class IOsmServerImpl : public QObject { virtual QUrl baseUrl() const { return QUrl(getServerInfo().Url); } + + enum class Error { + NoError, + Unauthorized, + Timeout, + ReplyError, + TokenRequestError + }; signals: void authenticated(); - void failed(int error); + void failed(Error error, QString errorString); }; Q_DECLARE_INTERFACE(IOsmServerImpl, "InterfaceIOsmServerImpl") From 16f65652fb07e4d9797fc843f9c31968542778e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sun, 14 Jul 2024 22:23:00 +0200 Subject: [PATCH 18/30] Added ifdef for Qt5. --- src/Utils/OsmServer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index de40d4b8..acef8ac3 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -248,11 +248,14 @@ void OsmServerImplOAuth2::authenticate() { emit failed(Error::ReplyError, tr("QOAuth2AuthorizationCodeFlow::error raised.")); }); +#if QT_VERSION > QT_VERSION_CHECK(6, 6, 0) + // TODO: Is there a way to check in Qt5? connect(replyHandler, &QOAuthHttpServerReplyHandler::tokenRequestErrorOccurred, this, [this, replyHandler](QAbstractOAuth::Error error, const QString& errorString) { qDebug() << "Token request error occurred: " << int(error) << errorString; replyHandler->deleteLater(); emit failed(Error::TokenRequestError, tr("Token request failed.") + "\n" + errorString); }); +#endif m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, auto*params){ /* Note: params is QMap for Qt5 and QMultiMap for Qt6 */ From aa807f3ba5b686e05a9876c12f4571233e4a13f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sun, 14 Jul 2024 22:33:10 +0200 Subject: [PATCH 19/30] Adjusted server URL text, added combo box with predefined server. --- src/Preferences/OsmServerWidget.ui | 31 ++++++++++++++++++++++++++- src/Preferences/PreferencesDialog.cpp | 4 ++-- src/Preferences/PreferencesDialog.ui | 28 +++++++++++++++++++----- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/Preferences/OsmServerWidget.ui b/src/Preferences/OsmServerWidget.ui index cb9cf96f..8b026fbb 100644 --- a/src/Preferences/OsmServerWidget.ui +++ b/src/Preferences/OsmServerWidget.ui @@ -36,7 +36,36 @@ - + + + + 0 + 0 + + + + IBeamCursor + + + true + + + true + + + true + + + + https://www.openstreetmap.org/ + + + + + https://master.apis.dev.openstreetmap.org/ + + + diff --git a/src/Preferences/PreferencesDialog.cpp b/src/Preferences/PreferencesDialog.cpp index 9a6442d0..b4663495 100644 --- a/src/Preferences/PreferencesDialog.cpp +++ b/src/Preferences/PreferencesDialog.cpp @@ -101,7 +101,7 @@ void OsmServerWidget::on_tbOsmServerDel_clicked() OsmServerInfo OsmServerWidget::getOsmServerInfo() const { OsmServerInfo srv; - srv.Url = edOsmServerUrl->text(); + srv.Url = edOsmServerUrl->currentText(); srv.Type = static_cast(authType->currentIndex()); srv.User = edOsmServerUser->text(); srv.Password = edOsmServerPwd->text(); @@ -110,7 +110,7 @@ OsmServerInfo OsmServerWidget::getOsmServerInfo() const { } void OsmServerWidget::setOsmServerInfo(OsmServerInfo srv) { - edOsmServerUrl->setText(srv.Url); + edOsmServerUrl->setCurrentText(srv.Url); authType->setCurrentIndex(static_cast(srv.Type)); edOsmServerUser->setText(srv.User); edOsmServerPwd->setText(srv.Password); diff --git a/src/Preferences/PreferencesDialog.ui b/src/Preferences/PreferencesDialog.ui index 30977e1c..f341ee97 100644 --- a/src/Preferences/PreferencesDialog.ui +++ b/src/Preferences/PreferencesDialog.ui @@ -8,7 +8,7 @@ 0 0 689 - 487 + 592 @@ -21,7 +21,7 @@ QTabWidget::North - 3 + 5 @@ -899,7 +899,7 @@ - OSM API (URL is, e.g., "https://www.openstreetmap.org/api/0.6") + OSM API (do not include /api/0.6/ specifier) @@ -1121,7 +1121,16 @@ 4 - + + 0 + + + 0 + + + 0 + + 0 @@ -1149,7 +1158,16 @@ 4 - + + 0 + + + 0 + + + 0 + + 0 From b4c3cdf42e7d285d728507c7ebc3b8733355b014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 15 Jul 2024 21:20:59 +0200 Subject: [PATCH 20/30] Changed OAuth2 anum to OAuth2Redirect in preparation of adding OOB path. --- src/Utils/OsmServer.cpp | 3 ++- src/Utils/OsmServer.h | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index acef8ac3..cd1db25a 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -127,7 +127,8 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager) { if (info.Type == OsmServerInfo::AuthType::Basic) { return std::make_shared(info, manager); - } else if (info.Type == OsmServerInfo::AuthType::OAuth2) { + } else if (info.Type == OsmServerInfo::AuthType::OAuth2Redirect) { + // OsmServerImplOAuth2 will inspect info.Type and choose appropriately return std::make_shared(info, manager); } else { qWarning() << "Error creating OsmServer: Unknown AuthType" << static_cast(info.Type); diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index f8eb0c3b..dc0cf08a 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -15,19 +15,19 @@ struct OsmServerInfo /* Note: Types here correspond to the UI combo box. Keep those in sync. */ enum class AuthType : int{ Basic, - OAuth2 //password field is the token if this type is used + OAuth2Redirect, //password field is the token if OAuth2* is used }; static AuthType typeFromString(const QString& type) { if (type == "basic") return AuthType::Basic; - if (type == "oauth2") return AuthType::OAuth2; + if (type == "oauth2redirect") return AuthType::OAuth2Redirect; qWarning() << "Error parsing AuthType: Unknown AuthType" << type; return AuthType::Basic; } static QString typeToString(AuthType type) { switch (type) { - case AuthType::OAuth2: return "oauth2"; + case AuthType::OAuth2Redirect: return "oauth2redirect"; case AuthType::Basic: return "basic"; default: qWarning() << "Error encoding AuthType: Unknown AuthType" << static_cast(type); From fe151fc5ed5715fd2658d84a3e392821a4138a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 15 Jul 2024 21:35:03 +0200 Subject: [PATCH 21/30] Basic OOB flow. --- CMakeLists.txt | 1 + src/Preferences/OsmServerWidget.ui | 9 +++-- src/Utils/OsmOAuth2Flow.cpp | 10 ++++++ src/Utils/OsmOAuth2Flow.h | 12 +++++++ src/Utils/OsmServer.cpp | 53 +++++++++++++++++++++++------- src/Utils/OsmServer.h | 3 ++ 6 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 src/Utils/OsmOAuth2Flow.cpp create mode 100644 src/Utils/OsmOAuth2Flow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d7e70ef1..d0271bca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ src/Utils/SlippyMapWidget.h src/Utils/Utils.h src/Utils/OsmLink.cpp src/Utils/OsmServer.cpp +src/Utils/OsmOAuth2Flow.cpp src/Utils/ShortcutOverrideFilter.cpp src/Utils/LineF.h src/Utils/SelectionDialog.ui diff --git a/src/Preferences/OsmServerWidget.ui b/src/Preferences/OsmServerWidget.ui index 8b026fbb..1f0fd199 100644 --- a/src/Preferences/OsmServerWidget.ui +++ b/src/Preferences/OsmServerWidget.ui @@ -103,7 +103,7 @@ - 1 + 0 @@ -112,7 +112,12 @@ - OAuth2 + OAuth2 Redirect (redirect via 127.0.0.1:1337) + + + + + OAuth2 OOB (manually copy code) diff --git a/src/Utils/OsmOAuth2Flow.cpp b/src/Utils/OsmOAuth2Flow.cpp new file mode 100644 index 00000000..9a3fc407 --- /dev/null +++ b/src/Utils/OsmOAuth2Flow.cpp @@ -0,0 +1,10 @@ +#include "OsmOAuth2Flow.h" + +OsmOAuth2Flow::OsmOAuth2Flow(QObject* parent) + : QOAuth2AuthorizationCodeFlow(parent) +{ } + +void OsmOAuth2Flow::requestAccessToken(const QString& code) { + return QOAuth2AuthorizationCodeFlow::requestAccessToken(code); +} + diff --git a/src/Utils/OsmOAuth2Flow.h b/src/Utils/OsmOAuth2Flow.h new file mode 100644 index 00000000..dd23f577 --- /dev/null +++ b/src/Utils/OsmOAuth2Flow.h @@ -0,0 +1,12 @@ +#include +#include + +class OsmOAuth2Flow : public QOAuth2AuthorizationCodeFlow +{ + Q_OBJECT + + public: + OsmOAuth2Flow(QObject* parent = nullptr); + void requestAccessToken(const QString& code); +}; + diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index cd1db25a..359b6102 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -3,8 +3,10 @@ #include #include #include +#include #include "OsmServer.h" +#include "OsmOAuth2Flow.h" #include // @@ -30,6 +32,7 @@ // // http://127.0.0.1:1337/ + class OsmServerImplBasic : public IOsmServerImpl { public: OsmServerImplBasic(OsmServerInfo& info, QNetworkAccessManager& manager) @@ -119,7 +122,7 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { private: OsmServerInfo m_info; QNetworkAccessManager& m_manager; - QOAuth2AuthorizationCodeFlow m_oauth2; + OsmOAuth2Flow m_oauth2; QString generateCodeVerifier(); }; @@ -130,6 +133,9 @@ std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAcces } else if (info.Type == OsmServerInfo::AuthType::OAuth2Redirect) { // OsmServerImplOAuth2 will inspect info.Type and choose appropriately return std::make_shared(info, manager); + } else if (info.Type == OsmServerInfo::AuthType::OAuth2OOB) { + // OsmServerImplOAuth2 will inspect info.Type and choose appropriately + return std::make_shared(info, manager); } else { qWarning() << "Error creating OsmServer: Unknown AuthType" << static_cast(info.Type); return nullptr; @@ -206,14 +212,26 @@ void OsmServerImplOAuth2::authenticate() { qDebug() << "Authorization URL: " << m_oauth2.authorizationUrl(); qDebug() << "Access Token URL: " << m_oauth2.accessTokenUrl(); - auto replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress("127.0.0.1"), 1337, this); - QTimer::singleShot(60000, replyHandler, [=]() { - qWarning() << "OAuth2 reply handler timed out."; - replyHandler->deleteLater(); - emit failed(Error::Timeout, tr("OAuth2 reply handler timed out.")); - }); + QString redirect; + + QAbstractOAuthReplyHandler* replyHandler = nullptr; + if (m_info.Type == OsmServerInfo::AuthType::OAuth2Redirect) { + replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress("127.0.0.1"), 1337, this); + QTimer::singleShot(60000, replyHandler, [=]() { + qWarning() << "OAuth2 reply handler timed out."; + replyHandler->deleteLater(); + emit failed(Error::Timeout, tr("OAuth2 reply handler timed out.")); + }); + + m_oauth2.setReplyHandler(replyHandler); + redirect = "http://127.0.0.1:1337/"; + } else { + replyHandler = new QOAuthOobReplyHandler(this); + m_oauth2.setReplyHandler(replyHandler); + redirect = "urn:ietf:wg:oauth:2.0:oob"; + } + - m_oauth2.setReplyHandler(replyHandler); QString codeVerifier = generateCodeVerifier(); QString codeChallenge = QString(QCryptographicHash::hash(codeVerifier.toUtf8(), QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); @@ -258,7 +276,7 @@ void OsmServerImplOAuth2::authenticate() { }); #endif - m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge](QAbstractOAuth::Stage stage, auto*params){ + m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge,redirect](QAbstractOAuth::Stage stage, auto*params){ /* Note: params is QMap for Qt5 and QMultiMap for Qt6 */ if (params) { qDebug() << "Stage: " << int(stage) << "with params" << *params; @@ -269,14 +287,14 @@ void OsmServerImplOAuth2::authenticate() { params->insert("code_challenge_method", "S256"); // FIXME: Can be replaced by ->replace when Qt 6.x is minimum supported version params->remove("redirect_uri"); - params->insert("redirect_uri", "http://127.0.0.1:1337/"); + params->insert("redirect_uri", redirect); break; case QAbstractOAuth::Stage::RequestingAccessToken: qDebug() << "Requesting Access Token."; params->insert("code_verifier", codeVerifier); //Note: technically, we no longer redirect, but OSM server rejects the token request unless we provide a matching redirect_uri params->remove("redirect_uri"); - params->insert("redirect_uri", "http://127.0.0.1:1337/"); + params->insert("redirect_uri", redirect); break; default: qDebug() << "default stage."; @@ -286,4 +304,17 @@ void OsmServerImplOAuth2::authenticate() { }); m_oauth2.grant(); + + if (m_info.Type == OsmServerInfo::AuthType::OAuth2OOB) { + bool ok; + QString text = QInputDialog::getText(nullptr, tr("Please login in the browser and copy the resulting code below:"), + tr("Code:"), QLineEdit::Normal, "", &ok); + if (ok && !text.isEmpty()) { + qDebug() << "Received code: " << text; + m_oauth2.requestAccessToken(text.trimmed()); + qDebug() << m_oauth2.accessTokenUrl(); + } else { + qDebug() << "Cancelled."; + } + } } diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index dc0cf08a..2438e3ec 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -16,11 +16,13 @@ struct OsmServerInfo enum class AuthType : int{ Basic, OAuth2Redirect, //password field is the token if OAuth2* is used + OAuth2OOB, }; static AuthType typeFromString(const QString& type) { if (type == "basic") return AuthType::Basic; if (type == "oauth2redirect") return AuthType::OAuth2Redirect; + if (type == "oauth2oob") return AuthType::OAuth2OOB; qWarning() << "Error parsing AuthType: Unknown AuthType" << type; return AuthType::Basic; } @@ -28,6 +30,7 @@ struct OsmServerInfo static QString typeToString(AuthType type) { switch (type) { case AuthType::OAuth2Redirect: return "oauth2redirect"; + case AuthType::OAuth2OOB: return "oauth2oob"; case AuthType::Basic: return "basic"; default: qWarning() << "Error encoding AuthType: Unknown AuthType" << static_cast(type); From 941e14458f93f1eb3c626a32476ea6708bd22d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 15 Jul 2024 21:45:22 +0200 Subject: [PATCH 22/30] Adjusted OOB flow to display URL. --- src/Utils/OsmServer.cpp | 37 +++++++++++++++++++++++-------------- src/Utils/OsmServer.h | 3 ++- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 359b6102..1ee13947 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -201,8 +201,6 @@ OsmServerImplOAuth2::OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessMana m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); } m_oauth2.setToken(m_info.Password); - - connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); } void OsmServerImplOAuth2::authenticate() { @@ -303,18 +301,29 @@ void OsmServerImplOAuth2::authenticate() { } }); - m_oauth2.grant(); - if (m_info.Type == OsmServerInfo::AuthType::OAuth2OOB) { - bool ok; - QString text = QInputDialog::getText(nullptr, tr("Please login in the browser and copy the resulting code below:"), - tr("Code:"), QLineEdit::Normal, "", &ok); - if (ok && !text.isEmpty()) { - qDebug() << "Received code: " << text; - m_oauth2.requestAccessToken(text.trimmed()); - qDebug() << m_oauth2.accessTokenUrl(); - } else { - qDebug() << "Cancelled."; - } + if (m_info.Type == OsmServerInfo::AuthType::OAuth2Redirect) { + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); + } else if (m_info.Type == OsmServerInfo::AuthType::OAuth2OOB) { + connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, [this](const QUrl& url){ + bool ok; + qDebug() << "Login URL: " << url; + QDesktopServices::openUrl(url); + QString text = QInputDialog::getText(nullptr, tr("OAuth2 OOB flow"), + tr("Please login on the following URL in the browser and copy the resulting code below:") + "\n" + url.toString(), + QLineEdit::Normal, "", &ok); + if (ok && !text.isEmpty()) { + qDebug() << "Received code: " << text; + m_oauth2.requestAccessToken(text.trimmed()); + qDebug() << m_oauth2.accessTokenUrl(); + } else { + qDebug() << "Cancelled by user."; + emit failed(Error::Cancelled, tr("Cancelled by user.")); + } + }); + } else { + qWarning() << "Unknown OAuth type."; } + + m_oauth2.grant(); } diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index 2438e3ec..a94593d2 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -66,7 +66,8 @@ class IOsmServerImpl : public QObject { Unauthorized, Timeout, ReplyError, - TokenRequestError + TokenRequestError, + Cancelled, }; signals: void authenticated(); From 8f296697f57c06fdb9d7c5ac37f2807ed2961d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 15 Jul 2024 22:08:19 +0200 Subject: [PATCH 23/30] Added missing guard. --- src/Utils/OsmOAuth2Flow.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Utils/OsmOAuth2Flow.h b/src/Utils/OsmOAuth2Flow.h index dd23f577..32f94de7 100644 --- a/src/Utils/OsmOAuth2Flow.h +++ b/src/Utils/OsmOAuth2Flow.h @@ -1,3 +1,6 @@ +#ifndef __OSMOAUTH2FLOW_H__ +#define __OSMOAUTH2FLOW_H__ + #include #include @@ -10,3 +13,4 @@ class OsmOAuth2Flow : public QOAuth2AuthorizationCodeFlow void requestAccessToken(const QString& code); }; +#endif // __OSMOAUTH2FLOW_H__ From b3745f04c0cec1c37e1c361490902cfa36ed6c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 15 Jul 2024 22:36:05 +0200 Subject: [PATCH 24/30] Changed all OsmServer logs to lc_OsmServer category. --- src/Utils/OsmServer.cpp | 58 +++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 1ee13947..10c69264 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "OsmServer.h" #include "OsmOAuth2Flow.h" @@ -32,6 +33,7 @@ // // http://127.0.0.1:1337/ +QLoggingCategory lc_OsmServer("merk.OsmServer"); class OsmServerImplBasic : public IOsmServerImpl { public: @@ -71,7 +73,7 @@ class OsmServerImplBasic : public IOsmServerImpl { qCritical() << "Error:" << reply->errorString(); emit failed(Error::Unauthorized, reply->errorString()); } else { - qDebug() << reply->readAll(); + qDebug(lc_OsmServer) << reply->readAll(); emit authenticated(); } }); @@ -97,22 +99,22 @@ class OsmServerImplOAuth2 : public IOsmServerImpl { OsmServerInfo const getServerInfo() const { return m_info; } virtual QNetworkReply* get(const QUrl &url) { - qDebug() << "get: " << baseUrl().resolved(url); + qDebug(lc_OsmServer) << "get: " << baseUrl().resolved(url); return m_oauth2.get(baseUrl().resolved(url)); } virtual QNetworkReply* put(const QUrl &url, const QByteArray &data) { - qDebug() << "put: " << baseUrl().resolved(url); + qDebug(lc_OsmServer) << "put: " << baseUrl().resolved(url); return m_oauth2.put(baseUrl().resolved(url), data); } virtual QNetworkReply* deleteResource(const QUrl &url) { - qDebug() << "delete: " << baseUrl().resolved(url); + qDebug(lc_OsmServer) << "delete: " << baseUrl().resolved(url); return m_oauth2.deleteResource(baseUrl().resolved(url)); } virtual QNetworkReply* sendRequest(QNetworkRequest &request, const QByteArray &verb, const QByteArray &data) { - qDebug() << "custom request" << request.url() << " with verb" << verb << "and data" << data; + qDebug(lc_OsmServer) << "custom request" << request.url() << " with verb" << verb << "and data" << data; m_oauth2.prepareRequest(&request, data); return m_manager.sendCustomRequest(request, verb, data); } @@ -137,7 +139,7 @@ std::shared_ptr makeOsmServer(OsmServerInfo& info, QNetworkAcces // OsmServerImplOAuth2 will inspect info.Type and choose appropriately return std::make_shared(info, manager); } else { - qWarning() << "Error creating OsmServer: Unknown AuthType" << static_cast(info.Type); + qWarning(lc_OsmServer) << "Error creating OsmServer: Unknown AuthType" << static_cast(info.Type); return nullptr; } } @@ -195,7 +197,7 @@ OsmServerImplOAuth2::OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessMana m_oauth2.setClientIdentifierSharedKey("SuWJYZr9hjCmZH4PaMrvB48aVR_vgIw4D2VUEKPlR4c"); } else { if (host != "openstreetmap.org") { - qWarning() << "Unknown OSM API host " << host << ", assuming alias to openstreetmap.org"; + qWarning(lc_OsmServer) << "Unknown OSM API host " << host << ", assuming alias to openstreetmap.org"; } m_oauth2.setClientIdentifier("wv3ui28EyHjH0c4C1Wuz6_I-o47ithPAOt7Qt1ov9Ps"); m_oauth2.setClientIdentifierSharedKey("evCjgZOGTRL70ezsXs3VbxG0ugjJ5hq7pFQMB6toBcM"); @@ -206,9 +208,9 @@ OsmServerImplOAuth2::OsmServerImplOAuth2(OsmServerInfo& info, QNetworkAccessMana void OsmServerImplOAuth2::authenticate() { m_oauth2.setAuthorizationUrl(baseUrl().resolved(QUrl("oauth2/authorize"))); m_oauth2.setAccessTokenUrl(baseUrl().resolved(QUrl("oauth2/token"))); - qDebug() << "Base URL: " << baseUrl(); - qDebug() << "Authorization URL: " << m_oauth2.authorizationUrl(); - qDebug() << "Access Token URL: " << m_oauth2.accessTokenUrl(); + qDebug(lc_OsmServer) << "Base URL: " << baseUrl(); + qDebug(lc_OsmServer) << "Authorization URL: " << m_oauth2.authorizationUrl(); + qDebug(lc_OsmServer) << "Access Token URL: " << m_oauth2.accessTokenUrl(); QString redirect; @@ -216,7 +218,7 @@ void OsmServerImplOAuth2::authenticate() { if (m_info.Type == OsmServerInfo::AuthType::OAuth2Redirect) { replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress("127.0.0.1"), 1337, this); QTimer::singleShot(60000, replyHandler, [=]() { - qWarning() << "OAuth2 reply handler timed out."; + qWarning(lc_OsmServer) << "OAuth2 reply handler timed out."; replyHandler->deleteLater(); emit failed(Error::Timeout, tr("OAuth2 reply handler timed out.")); }); @@ -235,9 +237,9 @@ void OsmServerImplOAuth2::authenticate() { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=]( QAbstractOAuth::Status status) { - qDebug() << "OAuth status changed to " << int(status); + qDebug(lc_OsmServer) << "OAuth status changed to " << int(status); if (status == QAbstractOAuth::Status::Granted) { - qDebug() << "Granted, token: " << m_oauth2.token(); + qDebug(lc_OsmServer) << "Granted, token: " << m_oauth2.token(); m_info.Password = m_oauth2.token(); emit authenticated(); replyHandler->deleteLater(); @@ -250,17 +252,17 @@ void OsmServerImplOAuth2::authenticate() { return; } - qDebug() << reply->readAll(); + qDebug(lc_OsmServer) << reply->readAll(); }); } else if (status == QAbstractOAuth::Status::TemporaryCredentialsReceived) { - qDebug() << "Temporary credentials."; + qDebug(lc_OsmServer) << "Temporary credentials."; } else { - qWarning() << "Status is not granted: " << int(status); + qWarning(lc_OsmServer) << "Status is not granted: " << int(status); } }); connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::error, replyHandler, [this, replyHandler]() { - qDebug() << "QOAuth2AuthorizationCodeFlow::error raised."; + qDebug(lc_OsmServer) << "QOAuth2AuthorizationCodeFlow::error raised."; replyHandler->deleteLater(); emit failed(Error::ReplyError, tr("QOAuth2AuthorizationCodeFlow::error raised.")); }); @@ -268,7 +270,7 @@ void OsmServerImplOAuth2::authenticate() { #if QT_VERSION > QT_VERSION_CHECK(6, 6, 0) // TODO: Is there a way to check in Qt5? connect(replyHandler, &QOAuthHttpServerReplyHandler::tokenRequestErrorOccurred, this, [this, replyHandler](QAbstractOAuth::Error error, const QString& errorString) { - qDebug() << "Token request error occurred: " << int(error) << errorString; + qDebug(lc_OsmServer) << "Token request error occurred: " << int(error) << errorString; replyHandler->deleteLater(); emit failed(Error::TokenRequestError, tr("Token request failed.") + "\n" + errorString); }); @@ -277,10 +279,10 @@ void OsmServerImplOAuth2::authenticate() { m_oauth2.setModifyParametersFunction([codeVerifier,codeChallenge,redirect](QAbstractOAuth::Stage stage, auto*params){ /* Note: params is QMap for Qt5 and QMultiMap for Qt6 */ if (params) { - qDebug() << "Stage: " << int(stage) << "with params" << *params; + qDebug(lc_OsmServer) << "Stage: " << int(stage) << "with params" << *params; switch (stage) { case QAbstractOAuth::Stage::RequestingAuthorization: - qDebug() << "Requesting Authorization."; + qDebug(lc_OsmServer) << "Requesting Authorization."; params->insert("code_challenge", codeChallenge); params->insert("code_challenge_method", "S256"); // FIXME: Can be replaced by ->replace when Qt 6.x is minimum supported version @@ -288,16 +290,16 @@ void OsmServerImplOAuth2::authenticate() { params->insert("redirect_uri", redirect); break; case QAbstractOAuth::Stage::RequestingAccessToken: - qDebug() << "Requesting Access Token."; + qDebug(lc_OsmServer) << "Requesting Access Token."; params->insert("code_verifier", codeVerifier); //Note: technically, we no longer redirect, but OSM server rejects the token request unless we provide a matching redirect_uri params->remove("redirect_uri"); params->insert("redirect_uri", redirect); break; default: - qDebug() << "default stage."; + qDebug(lc_OsmServer) << "default stage."; } - qDebug() << "Stage: " << int(stage) << "with params" << *params; + qDebug(lc_OsmServer) << "Stage: " << int(stage) << "with params" << *params; } }); @@ -307,22 +309,22 @@ void OsmServerImplOAuth2::authenticate() { } else if (m_info.Type == OsmServerInfo::AuthType::OAuth2OOB) { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, [this](const QUrl& url){ bool ok; - qDebug() << "Login URL: " << url; + qDebug(lc_OsmServer) << "Login URL: " << url; QDesktopServices::openUrl(url); QString text = QInputDialog::getText(nullptr, tr("OAuth2 OOB flow"), tr("Please login on the following URL in the browser and copy the resulting code below:") + "\n" + url.toString(), QLineEdit::Normal, "", &ok); if (ok && !text.isEmpty()) { - qDebug() << "Received code: " << text; + qDebug(lc_OsmServer) << "Received code: " << text; m_oauth2.requestAccessToken(text.trimmed()); - qDebug() << m_oauth2.accessTokenUrl(); + qDebug(lc_OsmServer) << m_oauth2.accessTokenUrl(); } else { - qDebug() << "Cancelled by user."; + qDebug(lc_OsmServer) << "Cancelled by user."; emit failed(Error::Cancelled, tr("Cancelled by user.")); } }); } else { - qWarning() << "Unknown OAuth type."; + qWarning(lc_OsmServer) << "Unknown OAuth type."; } m_oauth2.grant(); From 9f39f771766f10eefb5556cdd69ca88a3787c48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 15 Jul 2024 22:39:54 +0200 Subject: [PATCH 25/30] Added a hint about logging. --- HACKING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/HACKING.md b/HACKING.md index 3842a162..5be3dabf 100644 --- a/HACKING.md +++ b/HACKING.md @@ -83,4 +83,16 @@ The TCL framework also had the convention of "I" for second-level initialisation of objects after creation (possibly due to limitations in the compiler support of C++ features - I don't remember). +## Logging +Some of the (newer) classes use QLoggingCategory for log filtering. Debug logs are disabled by default, but can be enabled at runtime using environment variable: + +``` +QT_LOGGING_RULES="merk..debug=true" +``` + +Or for all classes: + +``` +QT_LOGGING_RULES="merk.*.debug=true" +``` From afa09bcf66317c515c31f8ff920534e1d7bb39ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 3 Aug 2024 23:41:58 +0200 Subject: [PATCH 26/30] Custom OAuth2 OOB dialog that actually allows to copy the link. --- CMakeLists.txt | 2 + src/Utils/OAuth2OOBDialog.cpp | 32 ++++++++ src/Utils/OAuth2OOBDialog.h | 20 +++++ src/Utils/OAuth2OOBDialog.ui | 144 ++++++++++++++++++++++++++++++++++ src/Utils/OsmServer.cpp | 13 ++- 5 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 src/Utils/OAuth2OOBDialog.cpp create mode 100644 src/Utils/OAuth2OOBDialog.h create mode 100644 src/Utils/OAuth2OOBDialog.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index d0271bca..63dd368e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,8 @@ src/Utils/Utils.h src/Utils/OsmLink.cpp src/Utils/OsmServer.cpp src/Utils/OsmOAuth2Flow.cpp +src/Utils/OAuth2OOBDialog.ui +src/Utils/OAuth2OOBDialog.cpp src/Utils/ShortcutOverrideFilter.cpp src/Utils/LineF.h src/Utils/SelectionDialog.ui diff --git a/src/Utils/OAuth2OOBDialog.cpp b/src/Utils/OAuth2OOBDialog.cpp new file mode 100644 index 00000000..2b718227 --- /dev/null +++ b/src/Utils/OAuth2OOBDialog.cpp @@ -0,0 +1,32 @@ +#include "OAuth2OOBDialog.h" + +#include +#include +#include +#include +#include + + +OAuth2OOBDialog::OAuth2OOBDialog(QWidget *parent) + : QDialog(parent) +{ + ui.setupUi(this); + + connect(ui.btnCopy, &QPushButton::clicked, this, [this](){ + QGuiApplication::clipboard()->setText(ui.loginUrl->text()); + }); + + connect(ui.btnOpen, &QPushButton::clicked, this, [this](){ + QDesktopServices::openUrl(QUrl(ui.loginUrl->text())); + }); +} + +QString OAuth2OOBDialog::getAuthCode(const QString url) { + OAuth2OOBDialog dialog; + dialog.ui.loginUrl->setText(url); + if (dialog.exec() == QDialog::Accepted) { + return dialog.ui.loginCode->text(); + } else { + return QString(); + } +} diff --git a/src/Utils/OAuth2OOBDialog.h b/src/Utils/OAuth2OOBDialog.h new file mode 100644 index 00000000..8b2d24c6 --- /dev/null +++ b/src/Utils/OAuth2OOBDialog.h @@ -0,0 +1,20 @@ +#ifndef __OAUTH2OOBDIALOG_H__ +#define __OAUTH2OOBDIALOG_H__ + +#include +#include "ui_OAuth2OOBDialog.h" + +class OAuth2OOBDialog : public QDialog +{ + Q_OBJECT + + public: + OAuth2OOBDialog(QWidget *parent = 0); + + static QString getAuthCode(const QString url); + + private: + Ui::OAuth2OOBDialog ui; +}; + +#endif diff --git a/src/Utils/OAuth2OOBDialog.ui b/src/Utils/OAuth2OOBDialog.ui new file mode 100644 index 00000000..78d600e1 --- /dev/null +++ b/src/Utils/OAuth2OOBDialog.ui @@ -0,0 +1,144 @@ + + + OAuth2OOBDialog + + + + 0 + 0 + 586 + 131 + + + + + 0 + 0 + + + + OAuth2 OOB + + + + + + + 0 + 0 + + + + Open the following URL in a browser. After logging in, paste the displayed authentication code below. + + + + + + + 5 + + + + + Login URL + + + + + + + + + true + + + true + + + + + + + + 0 + 0 + + + + Copy + + + + + + + Open + + + + + + + + + Authentication code + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + buttonBox + + + + + buttonBox + rejected() + OAuth2OOBDialog + reject() + + + 325 + 188 + + + 286 + 274 + + + + + buttonBox + accepted() + OAuth2OOBDialog + accept() + + + 292 + 224 + + + 292 + 122 + + + + + diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 10c69264..66315fa7 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -8,6 +8,7 @@ #include "OsmServer.h" #include "OsmOAuth2Flow.h" +#include "OAuth2OOBDialog.h" #include // @@ -308,15 +309,11 @@ void OsmServerImplOAuth2::authenticate() { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, &QDesktopServices::openUrl); } else if (m_info.Type == OsmServerInfo::AuthType::OAuth2OOB) { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, [this](const QUrl& url){ - bool ok; qDebug(lc_OsmServer) << "Login URL: " << url; - QDesktopServices::openUrl(url); - QString text = QInputDialog::getText(nullptr, tr("OAuth2 OOB flow"), - tr("Please login on the following URL in the browser and copy the resulting code below:") + "\n" + url.toString(), - QLineEdit::Normal, "", &ok); - if (ok && !text.isEmpty()) { - qDebug(lc_OsmServer) << "Received code: " << text; - m_oauth2.requestAccessToken(text.trimmed()); + QString code = OAuth2OOBDialog::getAuthCode(url.toString()); + if (!code.isNull()) { + qDebug(lc_OsmServer) << "Received code: " << code; + m_oauth2.requestAccessToken(code.trimmed()); qDebug(lc_OsmServer) << m_oauth2.accessTokenUrl(); } else { qDebug(lc_OsmServer) << "Cancelled by user."; From 4bb122219ddd67fe210b86c6547640a84a589ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Sat, 3 Aug 2024 23:59:47 +0200 Subject: [PATCH 27/30] Fixed invocation order for the OOB dialog. --- src/Preferences/PreferencesDialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Preferences/PreferencesDialog.cpp b/src/Preferences/PreferencesDialog.cpp index b4663495..b0639cb4 100644 --- a/src/Preferences/PreferencesDialog.cpp +++ b/src/Preferences/PreferencesDialog.cpp @@ -140,10 +140,11 @@ void OsmServerWidget::on_tbOAuth2Login_clicked() { connect(&*m_impl, &IOsmServerImpl::authenticated, this, &OsmServerWidget::on_httpAuthAuthenticated); connect(&*m_impl, &IOsmServerImpl::failed, this, &OsmServerWidget::on_httpAuthFailed); - m_impl->authenticate(); this->lbLoginState->setText(tr("Authenticating")); this->tbOAuth2Login->setEnabled(false); + + m_impl->authenticate(); } void OsmServerWidget::on_httpAuthAuthenticated() { From eb2ce640ad64c095fce645811f0a84d4bcdc0177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 26 Aug 2024 22:04:51 +0200 Subject: [PATCH 28/30] Added a simple migration logic. --- src/Preferences/MerkaartorPreferences.cpp | 13 +++++++++++ src/Utils/OsmServer.cpp | 27 +++++++++++++++++++++++ src/Utils/OsmServer.h | 3 +++ 3 files changed, 43 insertions(+) diff --git a/src/Preferences/MerkaartorPreferences.cpp b/src/Preferences/MerkaartorPreferences.cpp index c7063a70..25c7a16c 100644 --- a/src/Preferences/MerkaartorPreferences.cpp +++ b/src/Preferences/MerkaartorPreferences.cpp @@ -619,6 +619,17 @@ void MerkaartorPreferences::initialize() parentDashes << 1 << 5; + bool serversMigrated = false; + for (auto &srv: theOsmServers) { + serversMigrated |= migrateOsmServerInfo(srv); + } + if (serversMigrated) { + QMessageBox::information(nullptr, + tr("Osm Server Migration"), + tr("Some OSM server information has been migrated to the new format. Please visit preferences and login to the servers.")); + + } + //Ensure we have a CacheDir value in QSettings if (!g_Merk_Ignore_Preferences) Sets->setValue("backgroundImage/CacheDir", Sets->value("backgroundImage/CacheDir", HOMEDIR + "/BackgroundCache")); @@ -1585,6 +1596,7 @@ void MerkaartorPreferences::loadOsmServers() server.Url = Sets->value("url").toString(); server.User = Sets->value("user").toString(); server.Password = Sets->value("password").toString(); + server.CfgVersion = Sets->value("version").toInt(); theOsmServers.append(server); } Sets->endArray(); @@ -1602,6 +1614,7 @@ void MerkaartorPreferences::saveOsmServers() Sets->setValue("url", theOsmServers.at(i).Url); Sets->setValue("user", theOsmServers.at(i).User); Sets->setValue("password", theOsmServers.at(i).Password); + Sets->setValue("version", theOsmServers.at(i).CfgVersion); } Sets->endArray(); } diff --git a/src/Utils/OsmServer.cpp b/src/Utils/OsmServer.cpp index 66315fa7..c62bd749 100644 --- a/src/Utils/OsmServer.cpp +++ b/src/Utils/OsmServer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "OsmServer.h" #include "OsmOAuth2Flow.h" @@ -326,3 +327,29 @@ void OsmServerImplOAuth2::authenticate() { m_oauth2.grant(); } + +/** Migration logic */ + +bool migrateOsmServerInfo(OsmServerInfo& info) { + if (info.CfgVersion <= 0) { + /* Version 0 is pre-oAuth and old URL format. Fixup the URL and change to OAuth redirect. Ask user to migrate.*/ + auto newUrl = QString(info.Url).replace("http://", "https://").replace("/api/0.6", ""); + QMessageBox msgBox; + msgBox.setStyleSheet("QLabel{min-width: 500px;}"); + msgBox.setText(QObject::tr("Migrate OSM Server")); + msgBox.setInformativeText(QObject::tr("OSM.org now requires OAuth2 for authentication. This brings in a URL change. Do you want to migrate?\n\nCurrent URL: %1\nNew URL: %2").arg(info.Url, newUrl)); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + switch (msgBox.exec()) { + case QMessageBox::Yes: + info.Url = newUrl; + info.Type = OsmServerInfo::AuthType::OAuth2Redirect; + info.CfgVersion = 1; + return true; + default: + case QMessageBox::No: + return false; + } + } + return false; +} diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index a94593d2..7b4f4896 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -43,6 +43,7 @@ struct OsmServerInfo QString Url; QString User; QString Password; + int CfgVersion = 1; }; class IOsmServerImpl : public QObject { @@ -80,4 +81,6 @@ using OsmServer = std::shared_ptr; OsmServer makeOsmServer(OsmServerInfo& info, QNetworkAccessManager& manager); +bool migrateOsmServerInfo(OsmServerInfo& info); + #endif From df1e744a3e88bee6788862806435cb1e1efe112f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 26 Aug 2024 22:10:55 +0200 Subject: [PATCH 29/30] Removed HttpAuth test. --- CMakeLists.txt | 6 ------ tests/test-HttpAuth.cpp | 23 ----------------------- 2 files changed, 29 deletions(-) delete mode 100644 tests/test-HttpAuth.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 63dd368e..aa134cfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -484,7 +484,6 @@ option(LIBPROXY "Enable libproxy usage." option(WEBENGINE "Enable the use of QtWeb engine (not supported on all platforms)" OFF) option(PROTOBUF "Enable support for .osm.pbf format." ON) option(EXTRA_TESTS "Enable extra tests that cannot be run automatically on CI build." ON ) -option(INTERACTIVE_TESTS "Enable interactive tests that need user input." OFF) message(STATUS "Build options (use -DOPT=ON/OFF to enable/disable):") message(STATUS " * ZBAR ${ZBAR}") @@ -701,11 +700,6 @@ endfunction() MERK_ADD_TEST(test-projection src/common/Projection.cpp src/common/Coord.cpp) MERK_ADD_TEST(test-OsmLink src/Utils/OsmLink.cpp src/common/Coord.cpp) -# Additional tests that require user input -if (INTERACTIVE_TESTS) -MERK_ADD_TEST(test-HttpAuth src/Utils/HttpAuth.cpp) -endif() - # Additional tests that use the main executable if (EXTRA_TESTS) add_test(NAME "import-geojson" COMMAND ${PROJECT_NAME} --test-import ${CMAKE_SOURCE_DIR}/tests/data/sample.geojson) diff --git a/tests/test-HttpAuth.cpp b/tests/test-HttpAuth.cpp deleted file mode 100644 index d721d8d6..00000000 --- a/tests/test-HttpAuth.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -#include "Utils/HttpAuth.h" - -class TestHttpAuth : public QObject -{ - Q_OBJECT - private slots: - - - void simpleLogin() { - QUrl url("hello"); - qDebug() << url; - - HttpAuth auth(this); - auth.Login(); - auth.grant(); - QTest::qWait(10000); - } -}; - -QTEST_MAIN(TestHttpAuth) -#include "test-HttpAuth.moc" From ea793114f0ad337c32f9807a7e8c714c276bcf35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20L=C3=A1ska?= Date: Mon, 26 Aug 2024 22:17:06 +0200 Subject: [PATCH 30/30] Fixed OSM base url in goto dialog. --- src/Utils/OsmServer.h | 5 +++++ src/common/GotoDialog.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Utils/OsmServer.h b/src/Utils/OsmServer.h index 7b4f4896..1b23ff72 100644 --- a/src/Utils/OsmServer.h +++ b/src/Utils/OsmServer.h @@ -62,6 +62,11 @@ class IOsmServerImpl : public QObject { return QUrl(getServerInfo().Url); } + virtual QUrl apiUrl() const { + return QUrl(getServerInfo().Url).resolved(QUrl("/api/0.6")); + } + + enum class Error { NoError, Unauthorized, diff --git a/src/common/GotoDialog.cpp b/src/common/GotoDialog.cpp index 8fe9f0dd..93318884 100644 --- a/src/common/GotoDialog.cpp +++ b/src/common/GotoDialog.cpp @@ -69,7 +69,7 @@ GotoDialog::GotoDialog(MapView* aView, QWidget *parent) .arg(QString::number(OsmZoom)) ); coordOsmApi->setText( QString("%1/map?bbox=%2,%3,%4,%5") -// .arg(M_PREFS->getOsmApiUrl()) //FIXME: Wut? + .arg(M_PREFS->getOsmServer()->apiUrl().toString()) .arg(COORD2STRING(theViewport.bottomLeft().x())) .arg(COORD2STRING(theViewport.bottomLeft().y())) .arg(COORD2STRING(theViewport.topRight().x()))