[Git][debian-gis-team/pygeoapi][upstream] 2 commits: New upstream version 0.23.3

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Fri Jun 26 12:04:44 BST 2026



Bas Couwenberg pushed to branch upstream at Debian GIS Project / pygeoapi


Commits:
ee46986e by Bas Couwenberg at 2026-06-26T12:14:04+02:00
New upstream version 0.23.3
- - - - -
34393eae by Bas Couwenberg at 2026-06-26T12:14:54+02:00
New upstream version 0.23.4
- - - - -


11 changed files:

- docs/source/conf.py
- docs/source/configuration.rst
- pygeoapi/__init__.py
- pygeoapi/process/base.py
- pygeoapi/process/manager/base.py
- pygeoapi/provider/filesystem.py
- pygeoapi/resources/schemas/config/pygeoapi-config-0.x.yml
- pygeoapi/util.py
- requirements.txt
- tests/other/test_util.py
- tests/provider/test_filesystem_provider.py


Changes:

=====================================
docs/source/conf.py
=====================================
@@ -112,7 +112,7 @@ today_fmt = '%Y-%m-%d'
 # built documents.
 #
 # The short X.Y version.
-version = '0.23.2'
+version = '0.23.4'
 # The full version, including alpha/beta/rc tags.
 release = version
 


=====================================
docs/source/configuration.rst
=====================================
@@ -292,6 +292,10 @@ default.
           type: process  # REQUIRED (collection, process, or stac-collection)
           processor:
               name: HelloWorld  # Python path of process definition
+          # optional, allow for internal HTTP request execution
+          # if set to True, enables requests to link local ranges and loopback
+          # default: False
+          allow_internal_requests: True
 
 
 .. seealso::


=====================================
pygeoapi/__init__.py
=====================================
@@ -30,7 +30,7 @@
 #
 # =================================================================
 
-__version__ = '0.23.2'
+__version__ = '0.23.4'
 
 import click
 try:


=====================================
pygeoapi/process/base.py
=====================================
@@ -3,7 +3,7 @@
 # Authors: Tom Kralidis <tomkralidis at gmail.com>
 #          Francesco Martinelli <francesco.martinelli at ingv.it>
 #
-# Copyright (c) 2022 Tom Kralidis
+# Copyright (c) 2026 Tom Kralidis
 # Copyright (c) 2024 Francesco Martinelli
 #
 # Permission is hereby granted, free of charge, to any person
@@ -53,6 +53,8 @@ class BaseProcessor:
         self.name = processor_def['name']
         self.metadata = process_metadata
         self.supports_outputs = False
+        self.allow_internal_requests = processor_def.get(
+            'allow_internal_requests', False)
 
     def set_job_id(self, job_id: str) -> None:
         """


=====================================
pygeoapi/process/manager/base.py
=====================================
@@ -46,10 +46,12 @@ from pygeoapi.process.base import (
     BaseProcessor,
     JobNotFoundError,
     JobResultNotFoundError,
+    ProcessorExecuteError,
     UnknownProcessError,
 )
 from pygeoapi.util import (
     get_current_datetime,
+    is_request_allowed,
     JobStatus,
     ProcessExecutionMode,
     RequestedProcessExecutionMode,
@@ -105,7 +107,11 @@ class BaseManager:
         except KeyError as err:
             raise UnknownProcessError('Invalid process identifier') from err
         else:
-            return load_plugin('process', process_conf['processor'])
+            pp = load_plugin('process', process_conf['processor'])
+            pp.allow_internal_requests = process_conf.get(
+                'allow_internal_requests', False)
+
+            return pp
 
     def get_jobs(self,
                  status: JobStatus = None,
@@ -394,13 +400,13 @@ class BaseManager:
         """
 
         job_id = str(uuid.uuid1())
-        processor = self.get_processor(process_id)
-        processor.set_job_id(job_id)
+        self.processor = self.get_processor(process_id)
+        self.processor.set_job_id(job_id)
         extra_execute_handler_parameters = {
             'requested_response': requested_response
         }
 
-        job_control_options = processor.metadata.get(
+        job_control_options = self.processor.metadata.get(
             'jobControlOptions', [])
 
         if execution_mode == RequestedProcessExecutionMode.respond_async:
@@ -473,7 +479,7 @@ class BaseManager:
         # TODO: handler's response could also be allowed to include more HTTP
         # headers
         mime_type, outputs, status = handler(
-            processor,
+            self.processor,
             job_id,
             data_dict,
             requested_outputs,
@@ -483,26 +489,37 @@ class BaseManager:
 
     def _send_in_progress_notification(self, subscriber: Optional[Subscriber]):
         if subscriber and subscriber.in_progress_uri:
-            response = requests.post(subscriber.in_progress_uri, json={})
-            LOGGER.debug(
-                f'In progress notification response: {response.status_code}'
-            )
+            self.__do_subscriber_request(subscriber.in_progress_uri)
 
     def _send_success_notification(
             self, subscriber: Optional[Subscriber], outputs: Any
     ):
-        if subscriber:
-            response = requests.post(subscriber.success_uri, json=outputs)
-            LOGGER.debug(
-                f'Success notification response: {response.status_code}'
-            )
+        if subscriber and subscriber.success_uri:
+            self.__do_subscriber_request(subscriber.success_uri, outputs)
 
     def _send_failed_notification(self, subscriber: Optional[Subscriber]):
         if subscriber and subscriber.failed_uri:
-            response = requests.post(subscriber.failed_uri, json={})
-            LOGGER.debug(
-                f'Failed notification response: {response.status_code}'
-            )
+            self.__do_subscriber_request(subscriber.failed_uri)
+
+    def __do_subscriber_request(self, url: str, data: dict = {}) -> None:
+        """
+        Helper function to execute a subscriber URL via HTTP POST
+
+        :param url: `str` of URL
+        :param data: `dict` of request payload
+
+        :returns: `None`
+        """
+
+        if not is_request_allowed(url, self.processor.allow_internal_requests):
+            msg = 'URL not allowed'
+            LOGGER.error(f'{msg}: {url}')
+            raise ProcessorExecuteError(msg)
+
+        response = requests.post(url, json=data)
+        LOGGER.debug(
+            f'Response: {response.status_code}'
+        )
 
     def __repr__(self):
         return f'<BaseManager> {self.name}'


=====================================
pygeoapi/provider/filesystem.py
=====================================
@@ -2,7 +2,7 @@
 #
 # Authors: Tom Kralidis <tomkralidis at gmail.com>
 #
-# Copyright (c) 2023 Tom Kralidis
+# Copyright (c) 2026 Tom Kralidis
 #
 # Permission is hereby granted, free of charge, to any person
 # obtaining a copy of this software and associated documentation
@@ -34,6 +34,7 @@ import logging
 import os
 
 from pygeoapi.provider.base import (BaseProvider, ProviderConnectionError,
+                                    ProviderInvalidQueryError,
                                     ProviderNotFoundError)
 from pygeoapi.util import file_modified_iso8601, get_path_basename, url_join
 
@@ -76,9 +77,15 @@ class FileSystemProvider(BaseProvider):
         root_link = None
         child_links = []
 
-        data_path = os.path.join(self.data, dirpath)
+        if '..' in dirpath:
+            msg = 'Invalid path requested'
+            LOGGER.error(f'{msg}: {dirpath}')
+            raise ProviderInvalidQueryError(msg)
+
         data_path = self.data + dirpath
 
+        LOGGER.debug(f'Data path: {data_path}')
+
         if '/' not in dirpath:  # root
             root_link = baseurl
         else:


=====================================
pygeoapi/resources/schemas/config/pygeoapi-config-0.x.yml
=====================================
@@ -682,7 +682,11 @@ properties:
                                           For custom built plugins, use the import path (e.g. `mypackage.provider.MyProvider`)
                               required:
                                   - name
-                      required:
+                          allow_internal_requests:
+                              type: boolean
+                              description: whether to allow internal HTTP requests
+                              default: false
+                      requred:
                           - type
                           - processor
 definitions:


=====================================
pygeoapi/util.py
=====================================
@@ -36,6 +36,7 @@ from datetime import date, datetime, time, timezone
 from decimal import Decimal
 from enum import Enum
 from heapq import heappush
+import ipaddress
 import json
 import logging
 import mimetypes
@@ -43,6 +44,7 @@ import os
 import pathlib
 from pathlib import Path
 import re
+import socket
 from typing import Any, IO, Union, List, Optional
 from urllib.parse import urlparse
 from urllib.request import urlopen
@@ -754,3 +756,30 @@ def remove_url_auth(url: str) -> str:
     u = urlparse(url)
     auth = f'{u.username}:{u.password}@'
     return url.replace(auth, '')
+
+
+def is_request_allowed(url: str, allow_internal: bool = False) -> bool:
+    """
+    Test whether an HTTP request is allowed to be executed
+
+    :param url: `str` of URL
+    :param allow_internal: `bool` of whether internal requests are
+                           allowed (default `False`)
+
+    :returns: `bool` of whether HTTP request execution is allowed
+    """
+
+    is_allowed = False
+
+    u = urlparse(url)
+
+    ip = socket.gethostbyname(u.hostname)
+
+    is_private = ipaddress.ip_address(ip).is_private
+
+    if not is_private:
+        is_allowed = True
+    if is_private and allow_internal:
+        is_allowed = True
+
+    return is_allowed


=====================================
requirements.txt
=====================================
@@ -1,18 +1,18 @@
-Babel==2.10.3
-click==8.1.6
-filelock==3.25.0
-Flask==3.1.3
-jinja2==3.1.2
-jsonschema==4.26.0
-pydantic==1.10.14
-pygeofilter==0.3.3
-pygeoif==1.6.0
-pyproj==3.6.1
-python-dateutil==2.8.2
-pytz==2026.1.post1
-PyYAML==6.0.1
-rasterio==1.3.9
-requests==2.31.0
-shapely==2.0.3
-SQLAlchemy==2.0.48
-tinydb==3.15.2
+Babel
+click
+filelock
+Flask
+jinja2
+jsonschema
+pydantic
+pygeofilter
+pygeoif
+pyproj
+python-dateutil
+pytz
+PyYAML
+rasterio
+requests
+shapely
+SQLAlchemy
+tinydb


=====================================
tests/other/test_util.py
=====================================
@@ -2,7 +2,7 @@
 #
 # Authors: Tom Kralidis <tomkralidis at gmail.com>
 #
-# Copyright (c) 2025 Tom Kralidis
+# Copyright (c) 2026 Tom Kralidis
 #
 # Permission is hereby granted, free of charge, to any person
 # obtaining a copy of this software and associated documentation
@@ -321,3 +321,21 @@ def test_get_choice_from_headers():
                                         'accept') == 'application/ld+json'
     assert util.get_choice_from_headers(
         {'accept-language': 'en_US', 'accept': '*/*'}, 'accept') == '*/*'
+
+
+ at pytest.mark.parametrize('url,allow_internal,result', [
+    ['http://127.0.0.1/test', False, False],
+    ['http://127.0.0.1/test', True, True],
+    ['http://192.168.0.12/test', False, False],
+    ['http://192.168.0.12/test', True, True],
+    ['http://169.254.0.11/test', False, False],
+    ['http://169.254.0.11/test', True, True],
+    ['http://0.0.0.0/test', True, True],
+    ['http://0.0.0.0/test', False, False],
+    ['http://localhost:5000/test', False, False],
+    ['http://localhost:5000/test', True, True],
+    ['https://pygeoapi.io', False, True],
+    ['https://pygeoapi.io', True, True]
+])
+def test_is_request_allowed(url, allow_internal, result):
+    assert util.is_request_allowed(url, allow_internal) is result


=====================================
tests/provider/test_filesystem_provider.py
=====================================
@@ -2,7 +2,7 @@
 #
 # Authors: Tom Kralidis <tomkralidis at gmail.com>
 #
-# Copyright (c) 2021 Tom Kralidis
+# Copyright (c) 2026 Tom Kralidis
 #
 # Permission is hereby granted, free of charge, to any person
 # obtaining a copy of this software and associated documentation
@@ -30,6 +30,7 @@
 import os
 import pytest
 
+from pygeoapi.provider.base import ProviderInvalidQueryError
 from pygeoapi.provider.filesystem import FileSystemProvider
 
 THISDIR = os.path.dirname(os.path.realpath(__file__))
@@ -73,3 +74,6 @@ def test_query(config):
         'osm_id': 'int'
     }
     assert r['assets']['default']['href'] == 'http://example.org/stac/poi_portugal.gpkg'  # noqa
+
+    with pytest.raises(ProviderInvalidQueryError):
+        _ = p.get_data_path(baseurl, urlpath, '../../poi_portugal')



View it on GitLab: https://salsa.debian.org/debian-gis-team/pygeoapi/-/compare/459574bf9672ccfd3123a09cd4d3e1e4c6a3b487...34393eae89f654040fdc5b9f1f01bbc7aa5c1fe4

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pygeoapi/-/compare/459574bf9672ccfd3123a09cd4d3e1e4c6a3b487...34393eae89f654040fdc5b9f1f01bbc7aa5c1fe4
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260626/0bf6ff3a/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list