From ad737ed2ebc421c70dba32dcbe82391fe1a87de4 Mon Sep 17 00:00:00 2001 From: Richard Fairhurst Date: Sun, 16 Jun 2024 16:45:01 +0100 Subject: [PATCH] Allow changeset bounding boxes to be limited in size --- include/cgimap/options.hpp | 28 +++++++++++++ .../changeset_upload/changeset_updater.cpp | 7 ++++ src/main.cpp | 2 + src/options.cpp | 21 ++++++++++ test/test_apidb_backend_changeset_uploads.cpp | 39 ++++++++++++++++--- test/test_parse_options.cpp | 4 ++ 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/include/cgimap/options.hpp b/include/cgimap/options.hpp index 14de1e2b..f2145c9a 100644 --- a/include/cgimap/options.hpp +++ b/include/cgimap/options.hpp @@ -35,6 +35,8 @@ class global_settings_base { virtual uint32_t get_ratelimiter_ratelimit(bool) const = 0; virtual uint32_t get_ratelimiter_maxdebt(bool) const = 0; virtual bool get_ratelimiter_upload() const = 0; + virtual double get_bbox_max_size_1d() const = 0; + virtual double get_bbox_max_size_2d() const = 0; }; class global_settings_default : public global_settings_base { @@ -97,6 +99,14 @@ class global_settings_default : public global_settings_base { bool get_ratelimiter_upload() const override { return false; } + + double get_bbox_max_size_1d() const override { + return 360.0; + } + + double get_bbox_max_size_2d() const override { + return 180.0 * 360.0; + } }; class global_settings_via_options : public global_settings_base { @@ -175,6 +185,14 @@ class global_settings_via_options : public global_settings_base { return m_ratelimiter_upload; } + double get_bbox_max_size_1d() const override { + return m_bbox_max_size_1d; + } + + double get_bbox_max_size_2d() const override { + return m_bbox_max_size_2d; + } + private: void init_fallback_values(const global_settings_base &def); void set_new_options(const po::variables_map &options); @@ -191,6 +209,8 @@ class global_settings_via_options : public global_settings_base { void set_ratelimiter_ratelimit(const po::variables_map &options); void set_ratelimiter_maxdebt(const po::variables_map &options); void set_ratelimiter_upload(const po::variables_map &options); + void set_bbox_max_size_1d(const po::variables_map &options); + void set_bbox_max_size_2d(const po::variables_map &options); bool validate_timeout(const std::string &timeout) const; uint32_t m_payload_max_size; @@ -208,6 +228,8 @@ class global_settings_via_options : public global_settings_base { uint32_t m_ratelimiter_maxdebt; uint32_t m_moderator_ratelimiter_maxdebt; bool m_ratelimiter_upload; + double m_bbox_max_size_1d; + double m_bbox_max_size_2d; }; class global_settings final { @@ -256,6 +278,12 @@ class global_settings final { // Use ratelimiter for changeset uploads static bool get_ratelimiter_upload() { return settings->get_ratelimiter_upload(); } + // Maximum size of a bounding box in either dimension (lat or lon) + static double get_bbox_max_size_1d() { return settings->get_bbox_max_size_1d(); } + + // Maximum size of a bounding box in both dimensions (lat * lon) + static double get_bbox_max_size_2d() { return settings->get_bbox_max_size_2d(); } + private: static std::unique_ptr settings; // gets initialized with global_settings_default instance }; diff --git a/src/backend/apidb/changeset_upload/changeset_updater.cpp b/src/backend/apidb/changeset_upload/changeset_updater.cpp index 0caf45a1..9f32bd6b 100644 --- a/src/backend/apidb/changeset_upload/changeset_updater.cpp +++ b/src/backend/apidb/changeset_upload/changeset_updater.cpp @@ -119,6 +119,13 @@ void ApiDB_Changeset_Updater::update_changeset(const uint32_t num_new_changes, )"); if (valid_bbox) { + double dx = 1.0 * (cs_bbox.maxlon - cs_bbox.minlon) / global_settings::get_scale(); + double dy = 1.0 * (cs_bbox.maxlat - cs_bbox.minlat) / global_settings::get_scale(); + if (dy > global_settings::get_bbox_max_size_1d() || + dx > global_settings::get_bbox_max_size_1d() || + (dy * dx) > global_settings::get_bbox_max_size_2d()) { + throw http::bad_request("The changeset area is greater than the maximum allowed by this server."); + } auto r = m.exec_prepared("changeset_update_w_bbox", cs_num_changes, cs_bbox.minlat, cs_bbox.minlon, cs_bbox.maxlat, cs_bbox.maxlon, global_settings::get_changeset_timeout_open_max(), diff --git a/src/main.cpp b/src/main.cpp index 3260e2b0..5be3d31e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,6 +135,8 @@ void get_options(int argc, char **argv, po::variables_map &options) { ("max-relation-members", po::value(), "max number of relation members per relation") ("max-element-tags", po::value(), "max number of tags per OSM element") ("ratelimit-upload", po::value(), "enable rate limiting for changeset upload") + ("max-bbox-1d", po::value(), "max size in degrees of a changeset in either dimension") + ("max-bbox-2d", po::value(), "max size in square degrees of a changeset in both dimensions") ; // clang-format on diff --git a/src/options.cpp b/src/options.cpp index b9c8a718..30a487ea 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -33,6 +33,8 @@ void global_settings_via_options::init_fallback_values(const global_settings_bas m_ratelimiter_maxdebt = def.get_ratelimiter_maxdebt(false); m_moderator_ratelimiter_maxdebt = def.get_ratelimiter_maxdebt(true); m_ratelimiter_upload = def.get_ratelimiter_upload(); + m_bbox_max_size_1d = def.get_bbox_max_size_1d(); + m_bbox_max_size_2d = def.get_bbox_max_size_2d(); } void global_settings_via_options::set_new_options(const po::variables_map &options) { @@ -50,6 +52,8 @@ void global_settings_via_options::set_new_options(const po::variables_map &optio set_ratelimiter_ratelimit(options); set_ratelimiter_maxdebt(options); set_ratelimiter_upload(options); + set_bbox_max_size_1d(options); + set_bbox_max_size_2d(options); } void global_settings_via_options::set_payload_max_size(const po::variables_map &options) { @@ -185,6 +189,23 @@ void global_settings_via_options::set_ratelimiter_upload(const po::variables_map } } +void global_settings_via_options::set_bbox_max_size_1d(const po::variables_map &options) { + if (options.count("max-bbox-1d")) { + m_bbox_max_size_1d = options["max-bbox-1d"].as(); + if (m_bbox_max_size_1d < 0 || m_bbox_max_size_1d > 360.0) { + throw std::invalid_argument("max-bbox-1d (in degrees) must be between 0 and 360"); + } + } +} + +void global_settings_via_options::set_bbox_max_size_2d(const po::variables_map &options) { + if (options.count("max-bbox-2d")) { + m_bbox_max_size_1d = options["max-bbox-2d"].as(); + if (m_bbox_max_size_1d < 0 || m_bbox_max_size_1d > 360.0*180.0) { + throw std::invalid_argument("max-bbox-2d (in degrees) must be between 0 and 360*180"); + } + } +} bool global_settings_via_options::validate_timeout(const std::string &timeout) const { std::smatch sm; diff --git a/test/test_apidb_backend_changeset_uploads.cpp b/test/test_apidb_backend_changeset_uploads.cpp index 3e99b0f9..9d6667fe 100644 --- a/test/test_apidb_backend_changeset_uploads.cpp +++ b/test/test_apidb_backend_changeset_uploads.cpp @@ -53,6 +53,14 @@ class global_settings_enable_upload_rate_limiter_test_class : public global_sett bool get_ratelimiter_upload() const override { return true; } }; +class global_settings_bbox_size_test_class : public global_settings_default { + +public: + // limit bbox upload size + double get_bbox_max_size_1d() const override { return 1.0; } + double get_bbox_max_size_2d() const override { return 1.0; } +}; + std::unique_ptr getDocument(const std::string &document) { @@ -1945,6 +1953,9 @@ TEST_CASE_METHOD( DatabaseTestsFixture, "test_osmchange_message", "[changeset][u TEST_CASE_METHOD( DatabaseTestsFixture, "test_osmchange_end_to_end", "[changeset][upload][db]" ) { + auto test_settings = std::unique_ptr(new global_settings_bbox_size_test_class()); + global_settings::set_configuration(std::move(test_settings)); + const std::string bearertoken = "Bearer 4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8"; const std::string generator = "Test"; @@ -2150,13 +2161,13 @@ TEST_CASE_METHOD( DatabaseTestsFixture, "test_osmchange_end_to_end", "[changeset req.set_payload(R"( - + - + - + @@ -2232,11 +2243,11 @@ TEST_CASE_METHOD( DatabaseTestsFixture, "test_osmchange_end_to_end", "[changeset req.set_payload(R"( - - + + - + @@ -2398,6 +2409,22 @@ TEST_CASE_METHOD( DatabaseTestsFixture, "test_osmchange_end_to_end", "[changeset REQUIRE(req.response_status() == 200); } + SECTION("Try to upload data beyond the options-set maximum bbox size") { + + req.set_payload(R"( + + + + + )" ); + + // execute the request + process_request(req, limiter, generator, route, *sel_factory, upd_factory.get()); + + REQUIRE(req.response_status() == 400); + REQUIRE_THAT(req.body().str(), Equals("The changeset area is greater than the maximum allowed by this server.")); + } + } TEST_CASE_METHOD( DatabaseTestsFixture, "test_osmchange_rate_limiter", "[changeset][upload][db]" ) { diff --git a/test/test_parse_options.cpp b/test/test_parse_options.cpp index 2528dbf3..08b9c7b3 100644 --- a/test/test_parse_options.cpp +++ b/test/test_parse_options.cpp @@ -54,6 +54,8 @@ TEST_CASE("Set all supported options" "[options]") { vm.emplace("maxdebt", po::variable_value((long) 500, false)); vm.emplace("moderator-maxdebt", po::variable_value((long) 1000, false)); vm.emplace("ratelimit-upload", po::variable_value(true, false)); + vm.emplace("max-bbox-1d", po::variable_value((double) 3.5, false)); + vm.emplace("max-bbox-2d", po::variable_value((double) 16.0, false)); REQUIRE_NOTHROW(check_options(vm)); REQUIRE( global_settings::get_payload_max_size() == 40000 ); @@ -71,4 +73,6 @@ TEST_CASE("Set all supported options" "[options]") { REQUIRE( global_settings::get_ratelimiter_ratelimit(true) == 10000000 ); REQUIRE( global_settings::get_ratelimiter_maxdebt(true) == 1000l * 1024 * 1024 ); REQUIRE( global_settings::get_ratelimiter_upload() == true ); + REQUIRE( global_settings::get_bbox_max_size_1d() == 3.5 ); + REQUIRE( global_settings::get_bbox_max_size_2d() == 16.0 ); }