require 'httparty' require 'cgi' module Bluekai class Request attr_accessor :api_user_key, :api_private_key def initialize(method, path, query, body, domain) @method = method.upcase @path = path @query = query @body = body @domain = domain end def run response = http_request fail Bluekai::RequestFailed, response.body unless response.code.start_with?('2') return :success if response.body.nil? || response.body.strip.empty? JSON.parse(response.body, symbolize_names: true) end # HMAC-SHA256(Secret key, HTTP_METHOD + URI_PATH + QUERY_ARG_VALUES + POST_DATA) def signature digest = OpenSSL::Digest.new('sha256') hmac = OpenSSL::HMAC.digest(digest, api_private_key, string_to_sign) CGI.escape(Base64.strict_encode64(hmac)) end private attr_reader :method, :path, :query, :body, :domain def http_request case method when 'GET' HTTParty.get(url) when 'POST' HTTParty.post(url, non_get_params) when 'PUT' HTTParty.put(url, non_get_params) when 'DELETE' HTTParty.delete(url, non_get_params) end.response end def non_get_params { body: body_sorted, headers: json_headers } end def url "#{domain}#{path}?#{query_string}"\ "&bkuid=#{api_user_key}&bksig=#{signature}" end def body_sorted # sort hash according to keys for correct signature computation body.sort_by { |key, _value| key.to_s }.to_h.to_json if body end def json_headers { 'Accept' => 'application/json', 'Content-type' => 'application/json' } end def query_values return nil unless query query_string.split('&').map { |p| p.split('=', 2)[1] }.join end def query_string(params = nil, prefix = '') # converts a (nested) hash of query parameters into a query string. (params || query || {}) .map { |k, v| v.is_a?(Hash) ? query_string(v, "#{k}=") : "#{prefix}#{k}=#{v}" } .join('&') end def string_to_sign # see the example here: https://kb.bluekai.com/display/PD/Programming+Example method + path + query_values.to_s + body_sorted.to_s end end end