hooks-ruby 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hooks/app/api.rb +8 -10
- data/lib/hooks/app/endpoints/catchall.rb +9 -4
- data/lib/hooks/app/endpoints/health.rb +8 -2
- data/lib/hooks/app/endpoints/version.rb +8 -2
- data/lib/hooks/core/config_loader.rb +6 -1
- data/lib/hooks/core/config_validator.rb +1 -0
- data/lib/hooks/core/plugin_loader.rb +24 -7
- data/lib/hooks/plugins/auth/shared_secret.rb +4 -6
- data/lib/hooks/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27cbf50d13a78bab2fb359d40a11a8ffb6998712317a2927c95a1d083cdd3be3
|
4
|
+
data.tar.gz: b3473918310de8d23aeaf448af5d09b7601f18dce286a18cfcdc0efd78c7548a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8ec9dc8250c234bad0821d62f4876808725d0aa56ac8b62ba03c5789e02b250e11b0c8b8bba581e691033f130d69ef6d57f29ca2d4c29592389457e75107161
|
7
|
+
data.tar.gz: 0733db6077bfd15c6723cdad92e443a2c4d262f32ab6c30a989dcee12b07223a7d9bfff4a7fcb50950a1c412cb1c1e1a0031382f3d657d799e89da5a1f0d55dd
|
data/lib/hooks/app/api.rb
CHANGED
@@ -38,8 +38,8 @@ module Hooks
|
|
38
38
|
content_type :txt, "text/plain"
|
39
39
|
content_type :xml, "application/xml"
|
40
40
|
content_type :any, "*/*"
|
41
|
-
|
42
|
-
default_format :
|
41
|
+
|
42
|
+
default_format config[:default_format] || :json
|
43
43
|
end
|
44
44
|
|
45
45
|
api_class.class_eval do
|
@@ -117,22 +117,21 @@ module Hooks
|
|
117
117
|
log.info("successfully processed webhook event with handler: #{handler_class_name}")
|
118
118
|
log.debug("processing duration: #{Time.now - start_time}s")
|
119
119
|
status 200
|
120
|
-
|
121
|
-
response.to_json
|
120
|
+
response
|
122
121
|
rescue Hooks::Plugins::Handlers::Error => e
|
123
122
|
# Handler called error! method - immediately return error response and exit the request
|
124
123
|
log.debug("handler #{handler_class_name} called `error!` method")
|
125
124
|
|
126
|
-
error_response = nil
|
127
|
-
|
128
125
|
status e.status
|
129
126
|
case e.body
|
130
127
|
when String
|
128
|
+
# if error! was called with a string, we assume it's a simple text error
|
129
|
+
# example: error!("simple text error", 400) -> should return a plain text response
|
131
130
|
content_type "text/plain"
|
132
131
|
error_response = e.body
|
133
132
|
else
|
134
|
-
|
135
|
-
error_response = e.body
|
133
|
+
# Let Grape handle JSON conversion with the default format
|
134
|
+
error_response = e.body
|
136
135
|
end
|
137
136
|
|
138
137
|
return error_response
|
@@ -163,8 +162,7 @@ module Hooks
|
|
163
162
|
error_response[:handler] = handler_class_name unless config[:production]
|
164
163
|
|
165
164
|
status determine_error_code(e)
|
166
|
-
|
167
|
-
error_response.to_json
|
165
|
+
error_response
|
168
166
|
end
|
169
167
|
end
|
170
168
|
end
|
@@ -17,6 +17,13 @@ module Hooks
|
|
17
17
|
class CatchallEndpoint < Grape::API
|
18
18
|
include Hooks::App::Helpers
|
19
19
|
|
20
|
+
# Set up content types and default format to JSON to match main API
|
21
|
+
content_type :json, "application/json"
|
22
|
+
content_type :txt, "text/plain"
|
23
|
+
content_type :xml, "application/xml"
|
24
|
+
content_type :any, "*/*"
|
25
|
+
default_format :json
|
26
|
+
|
20
27
|
def self.mount_path(config)
|
21
28
|
# :nocov:
|
22
29
|
"#{config[:root_path]}/*path"
|
@@ -81,8 +88,7 @@ module Hooks
|
|
81
88
|
log.info("successfully processed webhook event with handler: #{handler_class_name}")
|
82
89
|
log.debug("processing duration: #{Time.now - start_time}s")
|
83
90
|
status 200
|
84
|
-
|
85
|
-
response.to_json
|
91
|
+
response
|
86
92
|
rescue StandardError => e
|
87
93
|
err_msg = "Error processing webhook event with handler: #{handler_class_name} - #{e.message} " \
|
88
94
|
"- request_id: #{request_id} - path: #{full_path} - method: #{http_method} - " \
|
@@ -102,8 +108,7 @@ module Hooks
|
|
102
108
|
error_response[:handler] = handler_class_name unless config[:production]
|
103
109
|
|
104
110
|
status determine_error_code(e)
|
105
|
-
|
106
|
-
error_response.to_json
|
111
|
+
error_response
|
107
112
|
end
|
108
113
|
end
|
109
114
|
end
|
@@ -6,14 +6,20 @@ require_relative "../../version"
|
|
6
6
|
module Hooks
|
7
7
|
module App
|
8
8
|
class HealthEndpoint < Grape::API
|
9
|
+
# Set up content types and default format to JSON
|
10
|
+
content_type :json, "application/json"
|
11
|
+
content_type :txt, "text/plain"
|
12
|
+
content_type :xml, "application/xml"
|
13
|
+
content_type :any, "*/*"
|
14
|
+
default_format :json
|
15
|
+
|
9
16
|
get do
|
10
|
-
content_type "application/json"
|
11
17
|
{
|
12
18
|
status: "healthy",
|
13
19
|
timestamp: Time.now.utc.iso8601,
|
14
20
|
version: Hooks::VERSION,
|
15
21
|
uptime_seconds: (Time.now - Hooks::App::API.server_start_time).to_i
|
16
|
-
}
|
22
|
+
}
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
@@ -6,12 +6,18 @@ require_relative "../../version"
|
|
6
6
|
module Hooks
|
7
7
|
module App
|
8
8
|
class VersionEndpoint < Grape::API
|
9
|
+
# Set up content types and default format to JSON
|
10
|
+
content_type :json, "application/json"
|
11
|
+
content_type :txt, "text/plain"
|
12
|
+
content_type :xml, "application/xml"
|
13
|
+
content_type :any, "*/*"
|
14
|
+
default_format :json
|
15
|
+
|
9
16
|
get do
|
10
|
-
content_type "application/json"
|
11
17
|
{
|
12
18
|
version: Hooks::VERSION,
|
13
19
|
timestamp: Time.now.utc.iso8601
|
14
|
-
}
|
20
|
+
}
|
15
21
|
end
|
16
22
|
end
|
17
23
|
end
|
@@ -20,7 +20,8 @@ module Hooks
|
|
20
20
|
production: true,
|
21
21
|
endpoints_dir: "./config/endpoints",
|
22
22
|
use_catchall_route: false,
|
23
|
-
normalize_headers: true
|
23
|
+
normalize_headers: true,
|
24
|
+
default_format: :json
|
24
25
|
}.freeze
|
25
26
|
|
26
27
|
SILENCE_CONFIG_LOADER_MESSAGES = ENV.fetch(
|
@@ -141,6 +142,7 @@ module Hooks
|
|
141
142
|
"HOOKS_ENDPOINTS_DIR" => :endpoints_dir,
|
142
143
|
"HOOKS_USE_CATCHALL_ROUTE" => :use_catchall_route,
|
143
144
|
"HOOKS_NORMALIZE_HEADERS" => :normalize_headers,
|
145
|
+
"HOOKS_DEFAULT_FORMAT" => :default_format,
|
144
146
|
"HOOKS_SOME_STRING_VAR" => :some_string_var # Added for test
|
145
147
|
}
|
146
148
|
|
@@ -155,6 +157,9 @@ module Hooks
|
|
155
157
|
when :use_catchall_route, :normalize_headers
|
156
158
|
# Convert string to boolean
|
157
159
|
env_config[config_key] = %w[true 1 yes on].include?(value.downcase)
|
160
|
+
when :default_format
|
161
|
+
# Convert string to symbol
|
162
|
+
env_config[config_key] = value.to_sym
|
158
163
|
else
|
159
164
|
env_config[config_key] = value
|
160
165
|
end
|
@@ -27,6 +27,7 @@ module Hooks
|
|
27
27
|
optional(:endpoints_dir).filled(:string)
|
28
28
|
optional(:use_catchall_route).filled(:bool)
|
29
29
|
optional(:normalize_headers).filled(:bool)
|
30
|
+
optional(:default_format).filled(:symbol, included_in?: %i[json txt xml any])
|
30
31
|
|
31
32
|
optional(:ip_filtering).hash do
|
32
33
|
optional(:ip_header).filled(:string)
|
@@ -205,7 +205,11 @@ module Hooks
|
|
205
205
|
require file_path
|
206
206
|
|
207
207
|
# Get the class and validate it
|
208
|
-
auth_plugin_class =
|
208
|
+
auth_plugin_class = begin
|
209
|
+
Hooks::Plugins::Auth.const_get(class_name, false) # false = don't inherit from ancestors
|
210
|
+
rescue NameError
|
211
|
+
raise StandardError, "Auth plugin class not found in Hooks::Plugins::Auth namespace: #{class_name}"
|
212
|
+
end
|
209
213
|
unless auth_plugin_class < Hooks::Plugins::Auth::Base
|
210
214
|
raise StandardError, "Auth plugin class must inherit from Hooks::Plugins::Auth::Base: #{class_name}"
|
211
215
|
end
|
@@ -239,8 +243,13 @@ module Hooks
|
|
239
243
|
# Load the file
|
240
244
|
require file_path
|
241
245
|
|
242
|
-
# Get the class and validate it
|
243
|
-
handler_class =
|
246
|
+
# Get the class and validate it - use safe constant lookup
|
247
|
+
handler_class = begin
|
248
|
+
# Check if the constant exists in the global namespace for handlers
|
249
|
+
Object.const_get(class_name, false) # false = don't inherit from ancestors
|
250
|
+
rescue NameError
|
251
|
+
raise StandardError, "Handler class not found: #{class_name}"
|
252
|
+
end
|
244
253
|
unless handler_class < Hooks::Plugins::Handlers::Base
|
245
254
|
raise StandardError, "Handler class must inherit from Hooks::Plugins::Handlers::Base: #{class_name}"
|
246
255
|
end
|
@@ -274,8 +283,12 @@ module Hooks
|
|
274
283
|
# Load the file
|
275
284
|
require file_path
|
276
285
|
|
277
|
-
# Get the class and validate it
|
278
|
-
lifecycle_class =
|
286
|
+
# Get the class and validate it - use safe constant lookup
|
287
|
+
lifecycle_class = begin
|
288
|
+
Object.const_get(class_name, false) # false = don't inherit from ancestors
|
289
|
+
rescue NameError
|
290
|
+
raise StandardError, "Lifecycle plugin class not found: #{class_name}"
|
291
|
+
end
|
279
292
|
unless lifecycle_class < Hooks::Plugins::Lifecycle
|
280
293
|
raise StandardError, "Lifecycle plugin class must inherit from Hooks::Plugins::Lifecycle: #{class_name}"
|
281
294
|
end
|
@@ -309,8 +322,12 @@ module Hooks
|
|
309
322
|
# Load the file
|
310
323
|
require file_path
|
311
324
|
|
312
|
-
# Get the class and validate it
|
313
|
-
instrument_class =
|
325
|
+
# Get the class and validate it - use safe constant lookup
|
326
|
+
instrument_class = begin
|
327
|
+
Object.const_get(class_name, false) # false = don't inherit from ancestors
|
328
|
+
rescue NameError
|
329
|
+
raise StandardError, "Instrument plugin class not found: #{class_name}"
|
330
|
+
end
|
314
331
|
|
315
332
|
# Determine instrument type based on inheritance
|
316
333
|
if instrument_class < Hooks::Plugins::Instruments::StatsBase
|
@@ -68,23 +68,21 @@ module Hooks
|
|
68
68
|
secret_header = validator_config[:header]
|
69
69
|
|
70
70
|
# Find the secret header with case-insensitive matching
|
71
|
-
|
71
|
+
provided_secret = find_header_value(headers, secret_header)
|
72
72
|
|
73
|
-
if
|
73
|
+
if provided_secret.nil? || provided_secret.empty?
|
74
74
|
log.warn("Auth::SharedSecret validation failed: Missing or empty secret header '#{secret_header}'")
|
75
75
|
return false
|
76
76
|
end
|
77
77
|
|
78
78
|
# Validate secret format using shared validation
|
79
|
-
unless valid_header_value?(
|
79
|
+
unless valid_header_value?(provided_secret, "Secret")
|
80
80
|
log.warn("Auth::SharedSecret validation failed: Invalid secret format")
|
81
81
|
return false
|
82
82
|
end
|
83
83
|
|
84
|
-
stripped_secret = raw_secret.strip
|
85
|
-
|
86
84
|
# Use secure comparison to prevent timing attacks
|
87
|
-
result = Rack::Utils.secure_compare(secret,
|
85
|
+
result = Rack::Utils.secure_compare(secret, provided_secret)
|
88
86
|
if result
|
89
87
|
log.debug("Auth::SharedSecret validation successful for header '#{secret_header}'")
|
90
88
|
else
|
data/lib/hooks/version.rb
CHANGED