Class: OctocatalogDiff::Util::HTTParty

Inherits:
Object
  • Object
show all
Defined in:
lib/octocatalog-diff/util/httparty.rb

Overview

This is a wrapper around some common actions that octocatalog-diff does when preparing to talk to a web server using 'httparty'.

Class Method Summary collapse

Class Method Details

.client_auth?(options) ⇒ Boolean

Determine, based on options, whether SSL client certificates need to be used. The order of precedence is:

  • If options is not nil, return it

  • If (key and cert) or PEM or PKCS12 are set, return true

  • Else return false

Returns:

  • (Boolean)

    see description


148
149
150
151
152
153
154
# File 'lib/octocatalog-diff/util/httparty.rb', line 148

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

.get(url, options = {}, ssl_prefix = nil) ⇒ Hash

Wrap the 'get' method in httparty with SSL options

Parameters:

  • url (String)

    URL to retrieve

  • options (Hash) (defaults to: {})

    Options

  • ssl_prefix (String) (defaults to: nil)

    Strip “#prefix_” from the start of SSL options to generalize them

Returns:

  • (Hash)

    HTTParty response and codes


17
18
19
# File 'lib/octocatalog-diff/util/httparty.rb', line 17

def self.get(url, options = {}, ssl_prefix = nil)
  httparty_response_parse(::HTTParty.get(url, options.merge(wrap_ssl_options(options, ssl_prefix))))
end

.httparty_response_parse(response) ⇒ Hash

Common parser for HTTParty response

Parameters:

  • response (HTTParty response object)

    HTTParty response object

Returns:

  • (Hash)

    HTTParty parsed response and codes


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
# File 'lib/octocatalog-diff/util/httparty.rb', line 35

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

.post(url, options, post_body, ssl_prefix) ⇒ Hash

Wrap the 'post' method in httparty with SSL options

Parameters:

  • url (String)

    URL to retrieve

  • options (Hash)

    Options

  • post_body (String)

    Test to POST

  • ssl_prefix (String)

    Strip “#prefix_” from the start of SSL options to generalize them

Returns:

  • (Hash)

    HTTParty response and codes


27
28
29
30
# File 'lib/octocatalog-diff/util/httparty.rb', line 27

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

.ssl_options(options) ⇒ Hash

SSL options to add to the httparty options hash

Parameters:

  • :ssl_ca (String)

    Optional: File with SSL CA certificate

  • :ssl_client_key (String)

    Full text of SSL client private key

  • :ssl_client_cert (String)

    Full text of SSL client public cert

  • :ssl_client_pem (String)

    Full text of SSL client private key + client public cert

  • :ssl_client_p12 (String)

    Full text of pkcs12-encoded keypair

  • :ssl_client_password (String)

    Password to unlock private key

Returns:

  • (Hash)

    Hash of SSL options to pass to httparty


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
# File 'lib/octocatalog-diff/util/httparty.rb', line 94

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

.ssl_verify?(options) ⇒ Boolean

Determine, based on options, whether SSL certificates should be verified. The order of precedence is:

  • If options is not nil, return it

  • If options is defined, return true

  • Else return false

Returns:

  • (Boolean)

    see description


162
163
164
165
# File 'lib/octocatalog-diff/util/httparty.rb', line 162

def self.ssl_verify?(options)
  return options[:ssl_verify] unless options[:ssl_verify].nil?
  options[:ssl_ca].is_a?(String)
end

.wrap_ssl_options(options, prefix) ⇒ Hash

Wrap context-specific options into generally named options for the other methods in this class

Parameters:

  • options (Hash)

    Hash of all options

  • prefix (String)

    Prefix to strip from SSL options

Returns:

  • (Hash)

    SSL options generally named


76
77
78
79
80
81
82
83
84
# File 'lib/octocatalog-diff/util/httparty.rb', line 76

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