lib/yoti/http/request.rb in yoti-1.6.4 vs lib/yoti/http/request.rb in yoti-1.7.0

- old
+ new

@@ -1,65 +1,129 @@ require 'securerandom' module Yoti # Manage the API's HTTPS requests class Request + # @deprecated will be removed in 2.0.0 - token is now provided with the endpoint # @return [String] the URL token received from Yoti Connect attr_accessor :encrypted_connect_token + # @return [String] the base URL + attr_writer :base_url + + # @return [Hash] query params to add to the request + attr_accessor :query_params + # @return [String] the HTTP method used for the request # The allowed methods are: GET, DELETE, POST, PUT, PATCH attr_accessor :http_method # @return [String] the API endpoint for the request attr_accessor :endpoint - # @return [Hash] the body sent with the request + # @return [#to_json,String] the body sent with the request attr_accessor :payload def initialize @headers = {} end + # + # @return [RequestBuilder] + # + def self.builder + RequestBuilder.new + end + + # # Adds a HTTP header to the request + # + # @param [String] header + # @param [String] value + # def add_header(header, value) @headers[header] = value end + # # Makes a HTTP request after signing the headers - # @return [Hash] the body from the HTTP request - def body + # + # @return [HTTPResponse] + # + def execute raise RequestError, 'The request requires a HTTP method.' unless @http_method - raise RequestError, 'The payload needs to be a hash.' unless @payload.to_s.empty? || @payload.is_a?(Hash) - res = Net::HTTP.start(uri.hostname, Yoti.configuration.api_port, use_ssl: https_uri?) do |http| + http_res = Net::HTTP.start(uri.hostname, Yoti.configuration.api_port, use_ssl: https_uri?) do |http| signed_request = SignedRequest.new(unsigned_request, path, @payload).sign http.request(signed_request) end - raise RequestError, "Unsuccessful Yoti API call: #{res.message}" unless res.code == '200' + raise RequestError.new("Unsuccessful Yoti API call: #{http_res.message}", http_res) unless response_is_success(http_res) - res.body + http_res end + # + # Makes a HTTP request and returns the body after signing the headers + # + # @return [String] + # + def body + execute.body + end + + # + # @return [String] the base URL + # + def base_url + @base_url ||= Yoti.configuration.api_endpoint + end + private + # + # @param [Net::HTTPResponse] http_res + # + # @return [Boolean] + # + def response_is_success(http_res) + http_res.code.to_i >= 200 && http_res.code.to_i < 300 + end + + # + # Adds payload to provided HTTP request + # + # @param [Net::HTTPRequest] http_req + # + def add_payload(http_req) + return if @payload.to_s.empty? + + if @payload.is_a?(String) + http_req.body = @payload + elsif @payload.respond_to?(:to_json) + http_req.body = @payload.to_json + end + end + + # + # @return [Net::HTTPRequest] the unsigned HTTP request + # def unsigned_request case @http_method when 'GET' http_req = Net::HTTP::Get.new(uri) when 'DELETE' http_req = Net::HTTP::Delete.new(uri) when 'POST' http_req = Net::HTTP::Post.new(uri) - http_req.body = @payload.to_json unless @payload.to_s.empty? + add_payload(http_req) when 'PUT' http_req = Net::HTTP::Put.new(uri) - http_req.body = @payload.to_json unless @payload.to_s.empty? + add_payload(http_req) when 'PATCH' http_req = Net::HTTP::Patch.new(uri) - http_req.body = @payload.to_json unless @payload.to_s.empty? + add_payload(http_req) else raise RequestError, "Request method not allowed: #{@http_method}" end @headers.each do |header, value| @@ -67,32 +131,168 @@ end http_req end + # + # @return [URI] the full request URI + # def uri - @uri ||= URI(Yoti.configuration.api_endpoint + path) + @uri ||= URI(base_url + path) end + # + # @return [String] the path with query string + # def path @path ||= begin - nonce = SecureRandom.uuid - timestamp = Time.now.to_i - - "/#{@endpoint}/#{token}"\ - "?nonce=#{nonce}"\ - "&timestamp=#{timestamp}"\ - "&appId=#{Yoti.configuration.client_sdk_id}" + "/#{@endpoint}/#{token}".chomp('/') + "?#{query_string}" end end + # + # @deprecated will be removed in 2.0.0 - token is now provided with the endpoint + # + # @return [String] the decrypted connect token + # def token return '' unless @encrypted_connect_token Yoti::SSL.decrypt_token(@encrypted_connect_token) end def https_uri? uri.scheme == 'https' + end + + # + # Builds query string including nonce and timestamp + # + # @return [String] + # + def query_string + params = { + nonce: SecureRandom.uuid, + timestamp: Time.now.to_i + } + + if @query_params.nil? + # @deprecated this default will be removed in 2.0.0 + # Append appId when no custom query params are provided. + params.merge!(appId: Yoti.configuration.client_sdk_id) + else + Validation.assert_is_a(Hash, @query_params, 'query_params') + params.merge!(@query_params) + end + + params.map do |k, v| + CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s) + end.join('&') + end + end + + # + # Builder for {Request} + # + class RequestBuilder + def initialize + @headers = {} + @query_params = {} + end + + # + # Sets the base URL + # + # @param [String] base_url + # + # @return [self] + # + def with_base_url(base_url) + Validation.assert_is_a(String, base_url, 'base_url') + @base_url = base_url + self + end + + # + # Adds a HTTP header to the request + # + # @param [String] header + # @param [String] value + # + # @return [self] + # + def with_header(header, value) + Validation.assert_is_a(String, header, 'header') + Validation.assert_is_a(String, value, 'value') + @headers[header] = value + self + end + + # + # Adds a query parameter to the request + # + # @param [String] key + # @param [String] value + # + # @return [self] + # + def with_query_param(key, value) + Validation.assert_is_a(String, key, 'key') + Validation.assert_is_a(String, value, 'value') + @query_params[key] = value + self + end + + # + # Sets the HTTP method + # + # @param [String] http_method + # + # @return [self] + # + def with_http_method(http_method) + Validation.assert_is_a(String, http_method, 'http_method') + @http_method = http_method + self + end + + # + # Sets the API endpoint for the request + # + # @param [String] endpoint + # + # @return [self] + # + def with_endpoint(endpoint) + Validation.assert_is_a(String, endpoint, 'endpoint') + @endpoint = endpoint + self + end + + # + # Sets the body sent with the request + # + # @param [#to_json,String] payload + # + # @return [self] + # + def with_payload(payload) + Validation.assert_respond_to(:to_json, payload, 'payload') unless payload.is_a?(String) + @payload = payload + self + end + + # + # @return [Request] + # + def build + request = Request.new + request.base_url = @base_url + request.endpoint = @endpoint + request.query_params = @query_params + request.http_method = @http_method + request.payload = @payload + @headers.map { |k, v| request.add_header(k, v) } + request end end end