module Stripe::Webhook::Signature

def self.compute_signature(timestamp, payload, secret)

a payload, and a signing secret.
Computes a webhook signature given a time (probably the current time),
def self.compute_signature(timestamp, payload, secret)
  raise ArgumentError, "timestamp should be an instance of Time" \
    unless timestamp.is_a?(Time)
  raise ArgumentError, "payload should be a string" \
    unless payload.is_a?(String)
  raise ArgumentError, "secret should be a string" \
    unless secret.is_a?(String)
  timestamped_payload = "#{timestamp.to_i}.#{payload}"
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret,
                          timestamped_payload)
end

def self.generate_header(timestamp, signature, scheme: EXPECTED_SCHEME)

project and without).
mainly here for use in test cases (those that are both within this
Note that this isn't needed to verify webhooks in any way, and is

given webhook payload.
Generates a value that would be added to a `Stripe-Signature` for a
def self.generate_header(timestamp, signature, scheme: EXPECTED_SCHEME)
  raise ArgumentError, "timestamp should be an instance of Time" \
    unless timestamp.is_a?(Time)
  raise ArgumentError, "signature should be a string" \
    unless signature.is_a?(String)
  raise ArgumentError, "scheme should be a string" \
    unless scheme.is_a?(String)
  "t=#{timestamp.to_i},#{scheme}=#{signature}"
end

def self.get_timestamp_and_signatures(header, scheme)

from the header
Extracts the timestamp and the signature(s) with the desired scheme
def self.get_timestamp_and_signatures(header, scheme)
  list_items = header.split(/,\s*/).map { |i| i.split("=", 2) }
  timestamp = Integer(list_items.select { |i| i[0] == "t" }[0][1])
  signatures = list_items.select { |i| i[0] == scheme }.map { |i| i[1] }
  [Time.at(timestamp), signatures]
end

def self.verify_header(payload, header, secret, tolerance: nil)

Returns true otherwise

tolerance
- a tolerance is provided and the timestamp is not within the
- no signatures matching the expected signature
- no signatures found with the expected scheme
- the header does not match the expected format
Raises a SignatureVerificationError in the following cases:

Verifies the signature header for a given payload.
def self.verify_header(payload, header, secret, tolerance: nil)
  begin
    timestamp, signatures =
      get_timestamp_and_signatures(header, EXPECTED_SCHEME)
  # TODO: Try to knock over this blanket rescue as it can unintentionally
  # swallow many valid errors. Instead, try to validate an incoming
  # header one piece at a time, and error with a known exception class if
  # any part is found to be invalid. Rescue that class here.
  rescue StandardError
    raise SignatureVerificationError.new(
      "Unable to extract timestamp and signatures from header",
      header, http_body: payload
    )
  end
  if signatures.empty?
    raise SignatureVerificationError.new(
      "No signatures found with expected scheme #{EXPECTED_SCHEME}",
      header, http_body: payload
    )
  end
  expected_sig = compute_signature(timestamp, payload, secret)
  unless signatures.any? { |s| Util.secure_compare(expected_sig, s) }
    raise SignatureVerificationError.new(
      "No signatures found matching the expected signature for payload",
      header, http_body: payload
    )
  end
  if tolerance && timestamp < Time.now - tolerance
    raise SignatureVerificationError.new(
      "Timestamp outside the tolerance zone (#{Time.at(timestamp)})",
      header, http_body: payload
    )
  end
  true
end