33require "openssl"
44require "time"
55require_relative "base"
6+ require_relative "timestamp_validator"
67
78module Hooks
89 module Plugins
@@ -190,7 +191,6 @@ def self.normalize_headers(headers)
190191 # @return [Boolean] true if timestamp is valid or not required, false otherwise
191192 # @note Returns false if timestamp header is missing when required
192193 # @note Tolerance is applied as absolute difference (past or future)
193- # @note Tries ISO 8601 UTC format first, then falls back to Unix timestamp
194194 # @api private
195195 def self . valid_timestamp? ( headers , config )
196196 timestamp_header = config [ :timestamp_header ]
@@ -199,87 +199,16 @@ def self.valid_timestamp?(headers, config)
199199
200200 timestamp_value = headers [ timestamp_header . downcase ]
201201 return false unless timestamp_value
202- return false if timestamp_value . strip . empty?
203202
204- parsed_timestamp = parse_timestamp ( timestamp_value . strip )
205- return false unless parsed_timestamp . is_a? ( Integer )
206-
207- now = Time . now . utc . to_i
208- ( now - parsed_timestamp ) . abs <= tolerance
209- end
210-
211- # Parse timestamp value supporting both ISO 8601 UTC and Unix formats
212- #
213- # @param timestamp_value [String] The timestamp string to parse
214- # @return [Integer, nil] Epoch seconds if parsing succeeds, nil otherwise
215- # @note Security: Strict validation prevents various injection attacks
216- # @api private
217- def self . parse_timestamp ( timestamp_value )
218- # Reject if contains any control characters, whitespace, or null bytes
219- if timestamp_value =~ /[\u0000 -\u001F \u007F -\u009F ]/
220- log . warn ( "Auth::HMAC validation failed: Timestamp contains invalid characters" )
221- return nil
222- end
223- ts = parse_iso8601_timestamp ( timestamp_value )
224- return ts if ts
225- ts = parse_unix_timestamp ( timestamp_value )
226- return ts if ts
227-
228- # If neither format matches, return nil
229- log . warn ( "Auth::HMAC validation failed: Timestamp (#{ timestamp_value } ) is not valid ISO 8601 UTC or Unix format" )
230- return nil
231- end
232-
233- # Check if timestamp string looks like ISO 8601 UTC format (must have UTC indicator)
234- #
235- # @param timestamp_value [String] The timestamp string to check
236- # @return [Boolean] true if it appears to be ISO 8601 format (with or without UTC indicator)
237- # @api private
238- def self . iso8601_timestamp? ( timestamp_value )
239- !!( timestamp_value =~ /\A \d {4}-\d {2}-\d {2}[T ]\d {2}:\d {2}:\d {2}(?:\. \d +)?(Z|\+ 00:00|\+ 0000)?\z / )
240- end
241-
242- # Parse ISO 8601 UTC timestamp string (must have UTC indicator)
243- #
244- # @param timestamp_value [String] ISO 8601 timestamp string
245- # @return [Integer, nil] Epoch seconds if parsing succeeds, nil otherwise
246- # @note Only accepts UTC timestamps (ending with 'Z', '+00:00', '+0000')
247- # @api private
248- def self . parse_iso8601_timestamp ( timestamp_value )
249- if timestamp_value =~ /\A (\d {4}-\d {2}-\d {2}) (\d {2}:\d {2}:\d {2}(?:\. \d +)?)(?: )\+ 0000\z /
250- timestamp_value = "#{ $1} T#{ $2} +00:00"
251- end
252- # Ensure the timestamp explicitly includes a UTC indicator
253- return nil unless timestamp_value =~ /(Z|\+ 00:00|\+ 0000)\z /
254- return nil unless iso8601_timestamp? ( timestamp_value )
255- t = Time . parse ( timestamp_value ) rescue nil
256- return nil unless t
257- # The check for UTC indicator in regex makes this t.utc? or t.utc_offset == 0 redundant
258- # but kept for safety, though it should always be true now if Time.parse succeeds.
259- ( t . utc? || t . utc_offset == 0 ) ? t . to_i : nil
260- end
261-
262- # Parse Unix timestamp string (must be positive integer, no leading zeros except for "0")
263- #
264- # @param timestamp_value [String] Unix timestamp string
265- # @return [Integer, nil] Epoch seconds if parsing succeeds, nil otherwise
266- # @note Only accepts positive integer values, no leading zeros except for "0"
267- # @api private
268- def self . parse_unix_timestamp ( timestamp_value )
269- return nil unless unix_timestamp? ( timestamp_value )
270- ts = timestamp_value . to_i
271- return nil if ts <= 0
272- ts
203+ timestamp_validator . valid? ( timestamp_value , tolerance )
273204 end
274205
275- # Check if timestamp string looks like Unix timestamp format (no leading zeros except "0")
206+ # Get timestamp validator instance
276207 #
277- # @param timestamp_value [String] The timestamp string to check
278- # @return [Boolean] true if it appears to be Unix timestamp format
208+ # @return [TimestampValidator] Singleton timestamp validator instance
279209 # @api private
280- def self . unix_timestamp? ( timestamp_value )
281- return true if timestamp_value == "0"
282- !!( timestamp_value =~ /\A [1-9]\d *\z / )
210+ def self . timestamp_validator
211+ @timestamp_validator ||= TimestampValidator . new
283212 end
284213
285214 # Compute HMAC signature based on configuration requirements
0 commit comments