From 27fbe48e00669e7a638453abb5fe1384dbeaaaa4 Mon Sep 17 00:00:00 2001 From: Till Frankenbach Date: Mon, 30 Jun 2025 15:56:52 +0200 Subject: [PATCH 1/3] feat: add API key validation for selected provider --- ORStools/gui/ORStoolsDialog.py | 55 +++++++++++++++++++++++++++++-- ORStools/gui/ORStoolsDialogUI.ui | 7 ++++ ORStools/gui/img/icon_key.png | Bin 0 -> 1774 bytes 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 ORStools/gui/img/icon_key.png diff --git a/ORStools/gui/ORStoolsDialog.py b/ORStools/gui/ORStoolsDialog.py index 3d651098..dfad6764 100644 --- a/ORStools/gui/ORStoolsDialog.py +++ b/ORStools/gui/ORStoolsDialog.py @@ -32,7 +32,7 @@ from datetime import datetime from typing import Optional -from qgis.PyQt.QtWidgets import QCheckBox +from qgis.PyQt.QtNetwork import QNetworkRequest from ..utils.router import route_as_layer @@ -57,11 +57,20 @@ Qgis, # noqa: F811 QgsAnnotation, QgsCoordinateTransform, + QgsBlockingNetworkRequest, ) from qgis.gui import QgsMapCanvasAnnotationItem, QgsCollapsibleGroupBox, QgisInterface -from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication +from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication, QUrl from qgis.PyQt.QtGui import QTextDocument -from qgis.PyQt.QtWidgets import QAction, QDialog, QApplication, QMenu, QMessageBox, QDialogButtonBox +from qgis.PyQt.QtWidgets import ( + QAction, + QDialog, + QApplication, + QMenu, + QMessageBox, + QDialogButtonBox, + QCheckBox, +) from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtWidgets import ( QWidget, @@ -385,6 +394,7 @@ def __init__(self, iface: QgisInterface, parent=None) -> None: self.provider_config.setIcon(gui.GuiUtils.get_icon("icon_settings.png")) self.about_button.setIcon(gui.GuiUtils.get_icon("icon_about.png")) self.help_button.setIcon(gui.GuiUtils.get_icon("icon_help.png")) + self.check_key_button.setIcon(gui.GuiUtils.get_icon("icon_key.png")) # Connect signals to the color_duplicate_items function self.routing_fromline_list.model().rowsRemoved.connect( @@ -396,6 +406,8 @@ def __init__(self, iface: QgisInterface, parent=None) -> None: self.load_provider_combo_state() self.provider_combo.activated.connect(self.save_selected_provider_state) + self.provider_combo.currentIndexChanged.connect(self.set_check_provider_button) + self.check_key_button.clicked.connect(self.check_api_key) advanced_boxes = self.advances_group.findChildren(QgsCollapsibleGroupBox) for box in advanced_boxes: @@ -408,6 +420,43 @@ def __init__(self, iface: QgisInterface, parent=None) -> None: self.rubber_band = None + def set_check_provider_button(self): + """Checks, whether the current provider is of the public API and set a check provider if so""" + provider = configmanager.read_config()["providers"][self.provider_combo.currentIndex()] + url = provider.get("base_url", "") + if url == "https://api.openrouteservice.org": + self.check_key_button.show() + else: + self.check_key_button.hide() + + def check_api_key(self) -> None: + provider = configmanager.read_config()["providers"][self.provider_combo.currentIndex()] + api_key = provider.get("key", "") + + if not api_key: + QMessageBox.critical( + self, self.tr("Error"), self.tr("No API key set for the selected provider.") + ) + return + + url = f"https://api.openrouteservice.org/v2/directions/driving-car?api_key={api_key}&start=8.681495,49.41461&end=8.687872,49.420318" + request = QgsBlockingNetworkRequest() + qrequest = QNetworkRequest(QUrl(url)) + qrequest.setRawHeader( + b"Accept", + b"application/json, application/geo+json, application/gpx+xml, img/png; charset=utf-8", + ) + error_code = request.get(qrequest) + + if error_code == QgsBlockingNetworkRequest.ErrorCode.NoError: + QMessageBox.information(self, self.tr("Success"), self.tr("API key check successful!")) + else: + QMessageBox.critical( + self, + self.tr("API key Error"), + self.tr(f"API key check failed, key: {api_key} is invalid."), + ) + def _save_vertices_to_layer(self) -> None: """Saves the vertices list to a temp layer""" items = [ diff --git a/ORStools/gui/ORStoolsDialogUI.ui b/ORStools/gui/ORStoolsDialogUI.ui index 4b48c8a2..528e7292 100644 --- a/ORStools/gui/ORStoolsDialogUI.ui +++ b/ORStools/gui/ORStoolsDialogUI.ui @@ -169,6 +169,13 @@ + + + + Check if the API key for the selected provider is valid + + + diff --git a/ORStools/gui/img/icon_key.png b/ORStools/gui/img/icon_key.png new file mode 100644 index 0000000000000000000000000000000000000000..32f8298f0eeb63a98c48db3b2e8cc322a28891fa GIT binary patch literal 1774 zcmeHH`!^E`9N!Yw?yOdi^b#x;K07~(g;^)HvsT}#6d|Z=|fKe zR!$zS_>0o7$||aA>Kd9zZ5@>EDgDz1hDK=PvnHly7;~(Hlk+)(i>sSE(epR2i$1<2 zzf0GGg0F{!-i(NhqD05U#@&v;L%o-fm`rD6Jj%?<&dJStnqN@(tmx0;lINurl~vU> zFIenaPF;P&>o;xh-hXKC_}JOiGsG8+jE;>@PS1Xwn_pNIF0ZVvt$*9t+}hsR-GjHI zy7nal$qu&IG6Kkm0RTt{@mNcD%HU$|wmIAd-Yn|XQ?E!`v1bI*O?&4ojh>OUjQYtQ zhY^Ssq8EMgJYG54q4NDs?z%&O@J(%aU{4-%dpELsGqx-0yC5gL`=ff>SSVTvn#2WK z+PUV}wO!U>O(PO!`_)vr0u!xZKlaNl4nFGVXrbsp!jzsve3}8-dzE^8wOZlnl{Ukg zLzsc319`}8%HeqJ%GBe7)*oy8U1zS!=I#Z_yeA(~QRz;R9r-ui%pY=ed z&a0KwqnM+?DUlM8^ohyLm!iT53dKCyf+nffYXzS^v=$8s(Uf~)kr(uZCKZ=1f@p-% zy;7xA$6G66jv}+GrVN~Ss<;q;K5y_U!S z&`RopFf{ITr~p*|8o8j{MUQcs`Yk2t(SWgvRSVQKR#>2 z7r^eaLM6MOx>?jADWA(mW!(`dx=+}~!#Sh9TI+mFFa#-XQPB@*X9}2i`*o*E(TzLO z3;v{a1NI5bJGpaLQF1Tt%c!Uyo5={$PCVSGMQwO-qu!biXUbgFWhD73vpv~eW1fMA zN7Zo8!yy?}+%{fc@n4{U?RDu7P}azVaWH>cn1YqB$%*zis$Mo+CaQtsK`}VY?DA&r zLAuDw%o2u8qo)hDxFy_l*t|W_9Bg%vuW6E-qr@B?_S7Gwk&^E@xjo@H&wb`7oIylr z!;?V`R&joXTKTgzPs&6EJ~#pv5)w0S>PlY_Z!2ZGZr)Mu_XB89TmLRj13Flr%!4LX zL=sm_(TXYcx(sGzz<0~J@%>Iv$#KFTm|2}}4| z-K5tTerxbd7F@WI`4b3 Date: Wed, 9 Jul 2025 16:21:31 +0200 Subject: [PATCH 2/3] feat: add tests for api key check button --- tests/test_gui.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/test_gui.py b/tests/test_gui.py index d51a1728..bd5883df 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from qgis.PyQt.QtWidgets import QLineEdit from qgis.core import QgsSettings from qgis.gui import QgsCollapsibleGroupBox @@ -5,7 +7,7 @@ from qgis.PyQt.QtTest import QTest from qgis.PyQt.QtCore import Qt, QEvent, QPoint -from qgis.PyQt.QtWidgets import QPushButton +from qgis.PyQt.QtWidgets import QPushButton, QMessageBox from qgis.gui import QgsMapCanvas, QgsMapMouseEvent, QgsRubberBand from qgis.core import ( QgsCoordinateReferenceSystem, @@ -358,3 +360,51 @@ def test_ORStoolsDialogConfig_url(self): "POINT(8.67251100000000008 49.39887900000000087)", next(layer.getFeatures()).geometry().asPolyline()[0].asWkt(), ) + + def test_check_key_button(self): + from ORStools.gui.ORStoolsDialog import ORStoolsDialog + from ORStools.gui.ORStoolsDialogConfig import ORStoolsDialogConfigMain + + CRS = QgsCoordinateReferenceSystem.fromEpsgId(3857) + CANVAS.setExtent(QgsRectangle(258889, 7430342, 509995, 7661955)) + CANVAS.setDestinationCrs(CRS) + CANVAS.setFrameStyle(0) + CANVAS.resize(600, 400) + self.assertEqual(CANVAS.width(), 600) + self.assertEqual(CANVAS.height(), 400) + + dlg = ORStoolsDialog(IFACE) + dlg.open() + self.assertTrue(dlg.isVisible()) + + # Set and reset config to test whether the reset works + dlg_config = ORStoolsDialogConfigMain() + provider = dlg_config.providers.findChildren(QgsCollapsibleGroupBox)[0] + + # set endpoint of directions to non-existent value + line_edit = provider.findChild(QLineEdit, "openrouteservice_key_text") + api_key = line_edit.text() + line_edit.setText("notanapikey") + dlg_config.accept() + + settings_directions_endpoint = QgsSettings().value("ORStools/config")["providers"][0][ + "key" + ] + + self.assertEqual(settings_directions_endpoint, "notanapikey") + + with patch.object(QMessageBox, "information", return_value=QMessageBox.Ok) as mock_info, \ + patch.object(QMessageBox, "critical", return_value=QMessageBox.Ok) as mock_crit: + dlg.check_key_button.clicked.emit() + + dlg_config = ORStoolsDialogConfigMain() + provider = dlg_config.providers.findChildren(QgsCollapsibleGroupBox)[0] + + line_edit = provider.findChild(QLineEdit, "openrouteservice_key_text") + line_edit.setText(api_key) + dlg_config.accept() + + dlg.check_key_button.clicked.emit() + + mock_info.assert_called_once() + mock_crit.assert_called_once() From bc5b334b19c22f1ba8087f08b0a4df902c2f203b Mon Sep 17 00:00:00 2001 From: Till Frankenbach Date: Wed, 24 Sep 2025 10:12:30 +0200 Subject: [PATCH 3/3] docs: add to changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e38be6..922ff898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ RELEASING: 14. Create new release in GitHub with tag version and release title of `vX.X.X` --> +## Unreleased +### Added +- API key validation for selected provider ([#339](https://github.com/GIScience/orstools-qgis-plugin/issues/339)) + ## [2.0.1] - 2025-06-01 ### Added - Readd old icons and add new icons for processing algorithms