From dd92b1abd2ed91e2d29ae2c7c461530ef856a956 Mon Sep 17 00:00:00 2001
From: ulferts <jens.ulferts@googlemail.com>
Date: Fri, 21 Jun 2024 15:46:29 +0200
Subject: [PATCH 1/3] bump grape

---
 Gemfile                           |  2 +-
 Gemfile.lock                      | 17 +++++++----------
 lib/api/root.rb                   |  2 +-
 lib/api/utilities/grape_helper.rb |  6 +++++-
 4 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/Gemfile b/Gemfile
index 35b9e21a8add..7c912370a310 100644
--- a/Gemfile
+++ b/Gemfile
@@ -349,7 +349,7 @@ end
 gem "bootsnap", "~> 1.18.0", require: false
 
 # API gems
-gem "grape", "~> 2.0.0"
+gem "grape", "~> 2.1.0"
 gem "grape_logging", "~> 1.8.4"
 gem "roar", "~> 1.2.0"
 
diff --git a/Gemfile.lock b/Gemfile.lock
index ba216ee21a8b..b8d1335890f6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -591,13 +591,12 @@ GEM
       multi_json (~> 1.11)
       os (>= 0.9, < 2.0)
       signet (>= 0.16, < 2.a)
-    grape (2.0.0)
-      activesupport (>= 5)
-      builder
+    grape (2.1.0)
+      activesupport (>= 6)
       dry-types (>= 1.1)
-      mustermann-grape (~> 1.0.0)
-      rack (>= 1.3.0)
-      rack-accept
+      mustermann-grape (~> 1.1.0)
+      rack (>= 2)
+      zeitwerk
     grape_logging (1.8.4)
       grape
       rack
@@ -729,7 +728,7 @@ GEM
     multi_json (1.15.0)
     mustermann (3.0.0)
       ruby2_keywords (~> 0.0.1)
-    mustermann-grape (1.0.2)
+    mustermann-grape (1.1.0)
       mustermann (>= 1.0.0)
     mutex_m (0.2.0)
     net-http (0.4.1)
@@ -845,8 +844,6 @@ GEM
     raabro (1.4.0)
     racc (1.8.0)
     rack (2.2.9)
-    rack-accept (0.4.5)
-      rack (>= 0.4)
     rack-attack (6.7.0)
       rack (>= 1.0, < 4)
     rack-cors (2.0.2)
@@ -1211,7 +1208,7 @@ DEPENDENCIES
   good_job (= 3.26.2)
   google-apis-gmail_v1
   googleauth
-  grape (~> 2.0.0)
+  grape (~> 2.1.0)
   grape_logging (~> 1.8.4)
   grids!
   html-pipeline (~> 2.14.0)
diff --git a/lib/api/root.rb b/lib/api/root.rb
index 2fa907550dab..66f5067b387e 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -44,7 +44,7 @@ class Root < ::API::RootAPI
       api_error = ::API::Errors::Unauthenticated.new error_message
       representer = ::API::V3::Errors::ErrorRepresenter.new api_error
 
-      e.error_response status: 401, message: representer.to_json, headers: warden.headers, log: false
+      e.error! representer.to_json, 401, warden.headers
     end
 
     version "v3", using: :path do
diff --git a/lib/api/utilities/grape_helper.rb b/lib/api/utilities/grape_helper.rb
index 26a364e5bd62..e2c37f8e7cbf 100644
--- a/lib/api/utilities/grape_helper.rb
+++ b/lib/api/utilities/grape_helper.rb
@@ -38,6 +38,10 @@ def initialize(env)
           @env = env
           @options = {}
         end
+
+        def error!(message, status = nil, headers = nil, backtrace = nil, original_exception = nil)
+          super
+        end
       end
 
       def grape_error_for(env, api)
@@ -75,7 +79,7 @@ def default_error_response(headers, log)
             log.call(original_exception)
           end
 
-          error_response status: e.code, message: representer.to_json, headers: resp_headers
+          error!(representer.to_json, e.code, resp_headers)
         }
       end
     end

From 0669bd92d15b7cf1c101a811135708a5966b3065 Mon Sep 17 00:00:00 2001
From: ulferts <jens.ulferts@googlemail.com>
Date: Tue, 25 Jun 2024 17:05:05 +0200
Subject: [PATCH 2/3] return proper JSON errors on 404

---
 lib/api/v3/root.rb                                     |  6 ++++++
 .../requests/api/v3/budgets/budget_resource_spec.rb    |  4 +---
 .../api/cost_entries/cost_entry_resource_spec.rb       |  4 +---
 .../requests/api/cost_types/cost_type_resource_spec.rb |  4 +---
 .../requests/api/v3/meetings/meetings_resource_spec.rb |  4 +---
 spec/features/work_packages/navigation_spec.rb         |  2 +-
 spec/requests/api/v3/category_resource_spec.rb         | 10 ++--------
 .../api/v3/help_texts/help_texts_resource_spec.rb      |  5 +----
 spec/requests/api/v3/priority_resource_spec.rb         |  5 +----
 spec/requests/api/v3/status_resource_spec.rb           |  5 +----
 spec/requests/api/v3/types/type_resource_spec.rb       |  5 +----
 11 files changed, 17 insertions(+), 37 deletions(-)

diff --git a/lib/api/v3/root.rb b/lib/api/v3/root.rb
index 1d2e22b576cf..8deeb4b61d64 100644
--- a/lib/api/v3/root.rb
+++ b/lib/api/v3/root.rb
@@ -101,6 +101,12 @@ class Root < ::API::OpenProjectAPI
 
         API::OpenAPI.spec.to_yaml
       end
+
+      # Catch all unknown routes (therefore have it at the end of the file)
+      # and return a properly formatted 404 error.
+      route :any, "*path" do
+        raise API::Errors::NotFound
+      end
     end
   end
 end
diff --git a/modules/budgets/spec/requests/api/v3/budgets/budget_resource_spec.rb b/modules/budgets/spec/requests/api/v3/budgets/budget_resource_spec.rb
index 84c1815c1693..9e769c15723e 100644
--- a/modules/budgets/spec/requests/api/v3/budgets/budget_resource_spec.rb
+++ b/modules/budgets/spec/requests/api/v3/budgets/budget_resource_spec.rb
@@ -61,9 +61,7 @@
       context "invalid id" do
         let(:get_path) { api_v3_paths.budget "bogus" }
 
-        it_behaves_like "param validation error" do
-          let(:id) { "bogus" }
-        end
+        it_behaves_like "not found"
       end
     end
 
diff --git a/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb b/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb
index 5d7c81755513..094ffd7e7479 100644
--- a/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb
+++ b/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb
@@ -62,9 +62,7 @@
       context "invalid id" do
         let(:get_path) { api_v3_paths.cost_type "bogus" }
 
-        it_behaves_like "param validation error" do
-          let(:id) { "bogus" }
-        end
+        it_behaves_like "not found"
       end
     end
 
diff --git a/modules/costs/spec/requests/api/cost_types/cost_type_resource_spec.rb b/modules/costs/spec/requests/api/cost_types/cost_type_resource_spec.rb
index 763b914271eb..f66ce9964af5 100644
--- a/modules/costs/spec/requests/api/cost_types/cost_type_resource_spec.rb
+++ b/modules/costs/spec/requests/api/cost_types/cost_type_resource_spec.rb
@@ -67,9 +67,7 @@
       context "invalid id" do
         let(:get_path) { api_v3_paths.cost_type "bogus" }
 
-        it_behaves_like "param validation error" do
-          let(:id) { "bogus" }
-        end
+        it_behaves_like "not found"
       end
     end
 
diff --git a/modules/meeting/spec/requests/api/v3/meetings/meetings_resource_spec.rb b/modules/meeting/spec/requests/api/v3/meetings/meetings_resource_spec.rb
index 5d95cc0309ff..094095c8939d 100644
--- a/modules/meeting/spec/requests/api/v3/meetings/meetings_resource_spec.rb
+++ b/modules/meeting/spec/requests/api/v3/meetings/meetings_resource_spec.rb
@@ -69,9 +69,7 @@
       context "when invalid id" do
         let(:get_path) { api_v3_paths.budget "bogus" }
 
-        it_behaves_like "param validation error" do
-          let(:id) { "bogus" }
-        end
+        it_behaves_like "not found"
       end
     end
 
diff --git a/spec/features/work_packages/navigation_spec.rb b/spec/features/work_packages/navigation_spec.rb
index 6364eed3f47c..3375e552ec36 100644
--- a/spec/features/work_packages/navigation_spec.rb
+++ b/spec/features/work_packages/navigation_spec.rb
@@ -259,7 +259,7 @@
       visit "/projects/#{project.identifier}/work_packages?#{url_query}"
 
       wp_table.expect_toast message: "Your view is erroneous and could not be processed.", type: :error
-      expect(page).to have_css "li", text: "Bad request: id is invalid"
+      expect(page).to have_css "li", text: "The requested resource could not be found"
     end
   end
 end
diff --git a/spec/requests/api/v3/category_resource_spec.rb b/spec/requests/api/v3/category_resource_spec.rb
index b4ad3aaea0eb..b994ec4ce772 100644
--- a/spec/requests/api/v3/category_resource_spec.rb
+++ b/spec/requests/api/v3/category_resource_spec.rb
@@ -100,10 +100,7 @@
       context "invalid priority id" do
         let(:get_path) { api_v3_paths.category "bogus" }
 
-        it_behaves_like "param validation error" do
-          let(:id) { "bogus" }
-          let(:type) { "Category" }
-        end
+        it_behaves_like "not found"
       end
     end
 
@@ -116,10 +113,7 @@
         get get_path
       end
 
-      it_behaves_like "param validation error" do
-        let(:id) { "bogus" }
-        let(:type) { "Category" }
-      end
+      it_behaves_like "not found"
     end
   end
 end
diff --git a/spec/requests/api/v3/help_texts/help_texts_resource_spec.rb b/spec/requests/api/v3/help_texts/help_texts_resource_spec.rb
index 5a56e499fd93..cccdb84c9836 100644
--- a/spec/requests/api/v3/help_texts/help_texts_resource_spec.rb
+++ b/spec/requests/api/v3/help_texts/help_texts_resource_spec.rb
@@ -92,10 +92,7 @@
         context "invalid type id" do
           let(:get_path) { api_v3_paths.type "bogus" }
 
-          it_behaves_like "param validation error" do
-            let(:id) { "bogus" }
-            let(:type) { "HelpText" }
-          end
+          it_behaves_like "not found"
         end
 
         context "invisible type id" do
diff --git a/spec/requests/api/v3/priority_resource_spec.rb b/spec/requests/api/v3/priority_resource_spec.rb
index 38547bf77dc4..b7f93bdb39bf 100644
--- a/spec/requests/api/v3/priority_resource_spec.rb
+++ b/spec/requests/api/v3/priority_resource_spec.rb
@@ -86,10 +86,7 @@
       context "invalid priority id" do
         let(:get_path) { api_v3_paths.priority "bogus" }
 
-        it_behaves_like "param validation error" do
-          let(:id) { "bogus" }
-          let(:type) { "IssuePriority" }
-        end
+        it_behaves_like "not found"
       end
     end
 
diff --git a/spec/requests/api/v3/status_resource_spec.rb b/spec/requests/api/v3/status_resource_spec.rb
index 58bbd0bb25fa..f9c83ad19418 100644
--- a/spec/requests/api/v3/status_resource_spec.rb
+++ b/spec/requests/api/v3/status_resource_spec.rb
@@ -88,10 +88,7 @@
         context "invalid status id" do
           let(:get_path) { api_v3_paths.status "bogus" }
 
-          it_behaves_like "param validation error" do
-            let(:id) { "bogus" }
-            let(:type) { "Status" }
-          end
+          it_behaves_like "not found"
         end
       end
 
diff --git a/spec/requests/api/v3/types/type_resource_spec.rb b/spec/requests/api/v3/types/type_resource_spec.rb
index dd21d89a94e2..2b6934a37eb0 100644
--- a/spec/requests/api/v3/types/type_resource_spec.rb
+++ b/spec/requests/api/v3/types/type_resource_spec.rb
@@ -88,10 +88,7 @@
         context "invalid type id" do
           let(:get_path) { api_v3_paths.type "bogus" }
 
-          it_behaves_like "param validation error" do
-            let(:id) { "bogus" }
-            let(:type) { "Type" }
-          end
+          it_behaves_like "not found"
         end
       end
 

From 50b1ac7a3dacd0e805c8eb83409b3668a2ce8041 Mon Sep 17 00:00:00 2001
From: ulferts <jens.ulferts@googlemail.com>
Date: Tue, 25 Jun 2024 17:40:53 +0200
Subject: [PATCH 3/3] adapt content type on errors definition

---
 lib/api/root.rb                                 | 2 +-
 lib/api/utilities/grape_helper.rb               | 2 +-
 modules/bim/app/controllers/bim/bcf/api/root.rb | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/api/root.rb b/lib/api/root.rb
index 66f5067b387e..fe64cde5e91c 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -35,7 +35,7 @@ class Root < ::API::RootAPI
 
     parser :json, API::V3::Parser.new
 
-    error_representer ::API::V3::Errors::ErrorRepresenter, "hal+json"
+    error_representer ::API::V3::Errors::ErrorRepresenter, "application/hal+json; charset=utf-8"
     authentication_scope OpenProject::Authentication::Scope::API_V3
 
     OpenProject::Authentication.handle_failure(scope: API_V3) do |warden, _opts|
diff --git a/lib/api/utilities/grape_helper.rb b/lib/api/utilities/grape_helper.rb
index e2c37f8e7cbf..270e6227eb3c 100644
--- a/lib/api/utilities/grape_helper.rb
+++ b/lib/api/utilities/grape_helper.rb
@@ -71,7 +71,7 @@ def default_error_response(headers, log)
           original_exception = $!
           representer = error_representer.new e
           resp_headers = instance_exec &headers
-          env["api.format"] = error_content_type
+          resp_headers["Content-Type"] = error_content_type
 
           if log == true
             OpenProject.logger.error original_exception, reference: :APIv3
diff --git a/modules/bim/app/controllers/bim/bcf/api/root.rb b/modules/bim/app/controllers/bim/bcf/api/root.rb
index aac9d5a7f132..3fdcadc37d92 100644
--- a/modules/bim/app/controllers/bim/bcf/api/root.rb
+++ b/modules/bim/app/controllers/bim/bcf/api/root.rb
@@ -38,7 +38,7 @@ class Root < ::API::RootAPI
 
       default_format :json
 
-      error_representer ::Bim::Bcf::API::V2_1::Errors::ErrorRepresenter, :json
+      error_representer ::Bim::Bcf::API::V2_1::Errors::ErrorRepresenter, "application/json; charset=utf-8"
       error_formatter :json, ::Bim::Bcf::API::ErrorFormatter::Json
 
       authentication_scope OpenProject::Authentication::Scope::BCF_V2_1