This file is indexed.

/usr/lib/ruby/vendor_ruby/octocatalog-diff/util/httparty.rb is in octocatalog-diff 1.5.3-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# frozen_string_literal: true

require 'httparty'
require 'json'
require_relative '../external/pson/pure'

module OctocatalogDiff
  module Util
    # This is a wrapper around some common actions that octocatalog-diff does when preparing to talk
    # to a web server using 'httparty'.
    class HTTParty
      # Wrap the 'get' method in httparty with SSL options
      # @param url [String] URL to retrieve
      # @param options [Hash] Options
      # @param ssl_prefix [String] Strip "#{prefix}_" from the start of SSL options to generalize them
      # @return [Hash] HTTParty response and codes
      def self.get(url, options = {}, ssl_prefix = nil)
        httparty_response_parse(::HTTParty.get(url, options.merge(wrap_ssl_options(options, ssl_prefix))))
      end

      # Wrap the 'post' method in httparty with SSL options
      # @param url [String] URL to retrieve
      # @param options [Hash] Options
      # @param post_body [String] Test to POST
      # @param ssl_prefix [String] Strip "#{prefix}_" from the start of SSL options to generalize them
      # @return [Hash] HTTParty response and codes
      def self.post(url, options, post_body, ssl_prefix)
        opts = options.merge(wrap_ssl_options(options, ssl_prefix))
        httparty_response_parse(::HTTParty.post(url, opts.merge(body: post_body)))
      end

      # Common parser for HTTParty response
      # @param response [HTTParty response object] HTTParty response object
      # @return [Hash] HTTParty parsed response and codes
      def self.httparty_response_parse(response)
        # Handle HTTP errors
        unless response.code == 200
          begin
            b = JSON.parse(response.body)
            errormessage = b['error'] if b.is_a?(Hash) && b.key?('error')
          rescue JSON::ParserError
            errormessage = response.body
          ensure
            errormessage ||= response.body
          end
          return { code: response.code, body: response.body, error: errormessage }
        end

        # Handle success
        if response.headers.key?('content-type')
          if response.headers['content-type'] =~ %r{/json}
            begin
              return { code: 200, body: response.body, parsed: JSON.parse(response.body) }
            rescue JSON::ParserError => exc
              return { code: 500, body: response.body, error: "JSON parse error: #{exc.message}" }
            end
          end
          if response.headers['content-type'] =~ %r{/pson}
            begin
              return { code: 200, body: response.body, parsed: PSON.parse(response.body) }
            rescue PSON::ParserError => exc
              return { code: 500, body: response.body, error: "PSON parse error: #{exc.message}" }
            end
          end
          return { code: 500, body: response.body, error: "Don't know how to parse: #{response.headers['content-type']}" }
        end

        # Return raw output
        { code: response.code, body: response.body }
      end

      # Wrap context-specific options into generally named options for the other methods in this class
      # @param options [Hash] Hash of all options
      # @param prefix [String] Prefix to strip from SSL options
      # @return [Hash] SSL options generally named
      def self.wrap_ssl_options(options, prefix)
        return {} unless prefix
        result = {}
        options.keys.each do |key|
          next if key.to_s !~ /^#{prefix}_(ssl_.*)/
          result[Regexp.last_match[1].to_sym] = options[key]
        end
        ssl_options(result)
      end

      # SSL options to add to the httparty options hash
      # @param :ssl_ca [String] Optional: File with SSL CA certificate
      # @param :ssl_client_key [String] Full text of SSL client private key
      # @param :ssl_client_cert [String] Full text of SSL client public cert
      # @param :ssl_client_pem [String] Full text of SSL client private key + client public cert
      # @param :ssl_client_p12 [String] Full text of pkcs12-encoded keypair
      # @param :ssl_client_password [String] Password to unlock private key
      # @return [Hash] Hash of SSL options to pass to httparty
      def self.ssl_options(options)
        # Initialize the result
        result = {}

        # Verification of server against a known CA cert
        if ssl_verify?(options)
          result[:verify] = true
          raise ArgumentError, ':ssl_ca must be passed' unless options[:ssl_ca].is_a?(String)
          raise Errno::ENOENT, "'#{options[:ssl_ca]}' not a file" unless File.file?(options[:ssl_ca])
          result[:ssl_ca_file] = options[:ssl_ca]
        else
          result[:verify] = false
        end

        # SSL client certificate auth. This translates our options into httparty options.
        if client_auth?(options)
          if options[:ssl_client_key].is_a?(String) && options[:ssl_client_cert].is_a?(String)
            result[:pem] = options[:ssl_client_key] + options[:ssl_client_cert]
          elsif options[:ssl_client_pem].is_a?(String)
            result[:pem] = options[:ssl_client_pem]
          elsif options[:ssl_client_p12].is_a?(String)
            result[:p12] = options[:ssl_client_p12]
            raise ArgumentError, 'pkcs12 requires a password' unless options[:ssl_client_password]
            result[:p12_password] = options[:ssl_client_password]
          else
            raise ArgumentError, 'SSL client auth enabled but no client keypair specified'
          end

          # Make sure there's not a password required, or that if the password is given, it is correct.
          # This will raise OpenSSL::PKey::RSAError if the key needs a password.
          if result[:pem] && options[:ssl_client_password]
            result[:pem_password] = options[:ssl_client_password]
            _trash = OpenSSL::PKey::RSA.new(result[:pem], result[:pem_password])
          elsif result[:pem]
            # Ruby 2.4 requires a minimum password length of 4. If no password is needed for
            # the certificate, the specified password here is effectively ignored.
            # We do not want to wait on STDIN, so a password-protected certificate without a
            # password will cause this to raise an error. There are two checks here, to exclude
            # an edge case where somebody did actually put '1234' as their password.
            _trash = OpenSSL::PKey::RSA.new(result[:pem], '1234')
            _trash = OpenSSL::PKey::RSA.new(result[:pem], '5678')
          end
        end

        # Return result
        result
      end

      # Determine, based on options, whether SSL client certificates need to be used.
      # The order of precedence is:
      # - If options[:ssl_client_auth] is not nil, return it
      # - If (key and cert) or PEM or PKCS12 are set, return true
      # - Else return false
      # @return [Boolean] see description
      def self.client_auth?(options)
        return options[:ssl_client_auth] unless options[:ssl_client_auth].nil?
        return true if options[:ssl_client_cert].is_a?(String) && options[:ssl_client_key].is_a?(String)
        return true if options[:ssl_client_pem].is_a?(String)
        return true if options[:ssl_client_p12].is_a?(String)
        false
      end

      # Determine, based on options, whether SSL certificates should be verified.
      # The order of precedence is:
      # - If options[:ssl_verify] is not nil, return it
      # - If options[:ssl_ca] is defined, return true
      # - Else return false
      # @return [Boolean] see description
      def self.ssl_verify?(options)
        return options[:ssl_verify] unless options[:ssl_verify].nil?
        options[:ssl_ca].is_a?(String)
      end
    end
  end
end