<commit_before># frozen_string_literal: true

# Implemented according to HTTP signatures (Draft 6)
# <https://tools.ietf.org/html/draft-cavage-http-signatures-06>
module SignatureVerification
  extend ActiveSupport::Concern

  def signed_request?
    request.headers['Signature'].present?
  end

  def signature_verification_failure_reason
    return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
  end

  def signed_request_account
    return @signed_request_account if defined?(@signed_request_account)

    unless signed_request?
      @signature_verification_failure_reason = 'Request not signed'
      @signed_request_account = nil
      return
    end

    if request.headers['Date'].present? && !matches_time_window?
      @signature_verification_failure_reason = 'Signed request date outside acceptable time window'
      @signed_request_account = nil
      return
    end

    raw_signature    = request.headers['Signature']
    signature_params = {}

    raw_signature.split(',').each do |part|
      parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
      next if parsed_parts.nil? || parsed_parts.size != 3
      signature_params[parsed_parts[1]] = parsed_parts[2]
    end

    if incompatible_signature?(signature_params)
      @signature_verification_failure_reason = 'Incompatible request signature'
      @signed_request_account = nil
      return
    end

    account_stoplight = Stoplight("source:#{request.ip}") { account_from_key_id(signature_params['keyId']) }
      .with_fallback { nil }
      .with_threshold(1)
      .with_cool_off_time(5.minutes.seconds)

    account = account_stoplight.run

    if account.nil?
      @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
      @signed_request_account = nil
      return
    end

    signature             = Base64.decode64(signature_params['signature'])
    compare_signed_string = build_signed_string(signature_params['headers'])

    if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
      @signed_request_account = account
      @signed_request_account
    elsif account.possibly_stale?
      account = account.refresh!

      if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
        @signed_request_account = account
        @signed_request_account
      else
        @signature_verification_failure_reason = "Verification failed for #{account.<EMAIL>}@#{account.domain} #{account.uri}"
        @signed_request_account = nil
      end
    else
      @signature_verification_failure_reason = "Verification failed for #{account.<EMAIL>}@#{account.domain} #{account.uri}"<commit_msg>Fix signature verification stoplight triggering on non-timeout errors (#9617)<commit_after># frozen_string_literal: true

# Implemented according to HTTP signatures (Draft 6)
# <https://tools.ietf.org/html/draft-cavage-http-signatures-06>
module SignatureVerification
  extend ActiveSupport::Concern

  def signed_request?
    request.headers['Signature'].present?
  end

  def signature_verification_failure_reason
    return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
  end

  def signed_request_account
    return @signed_request_account if defined?(@signed_request_account)

    unless signed_request?
      @signature_verification_failure_reason = 'Request not signed'
      @signed_request_account = nil
      return
    end

    if request.headers['Date'].present? && !matches_time_window?
      @signature_verification_failure_reason = 'Signed request date outside acceptable time window'
      @signed_request_account = nil
      return
    end

    raw_signature    = request.headers['Signature']
    signature_params = {}

    raw_signature.split(',').each do |part|
      parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
      next if parsed_parts.nil? || parsed_parts.size != 3
      signature_params[parsed_parts[1]] = parsed_parts[2]
    end

    if incompatible_signature?(signature_params)
      @signature_verification_failure_reason = 'Incompatible request signature'
      @signed_request_account = nil
      return
    end

    account_stoplight = Stoplight("source:#{request.ip}") { account_from_key_id(signature_params['keyId']) }
      .with_fallback { nil }
      .with_threshold(1)
      .with_cool_off_time(5.minutes.seconds)
      .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }

    account = account_stoplight.run

    if account.nil?
      @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
      @signed_request_account = nil
      return
    end

    signature             = Base64.decode64(signature_params['signature'])
    compare_signed_string = build_signed_string(signature_params['headers'])

    if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
      @signed_request_account = account
      @signed_request_account
    elsif account.possibly_stale?
      account = account.refresh!

      if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
        @signed_request_account = account
        @signed_request_account
      else
        @signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
        @signed_request_account = nil
      end
    else
      @signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
{'fraction_non_alphanumeric': 0.09663791585988382, 'fraction_numerical': 0.0070410139060024645, 'mean_word_length': 4.160762942779291, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1079784', 'n_tokens_mistral': 1668, 'n_tokens_neox': 1611, 'n_words': 303}