File: rest.rb

package info (click to toggle)
ruby-telesign 2.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 104 kB
  • sloc: ruby: 254; makefile: 4
file content (263 lines) | stat: -rw-r--r-- 9,324 bytes parent folder | download
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
require 'pp'
require 'json'
require 'time'
require 'base64'
require 'openssl'
require 'securerandom'
require 'net/http/persistent'
require_relative 'constants'

module Telesign

  # The TeleSign RestClient is a generic HTTP REST client that can be extended to make requests against any of
  # TeleSign's REST API endpoints.
  #
  # RequestEncodingMixin offers the function _encode_params for url encoding the body for use in string_to_sign outside
  # of a regular HTTP request.
  #
  # See https://developer.telesign.com for detailed API documentation.
  class RestClient

    # A simple HTTP Response object to abstract the underlying net/http library response.

    # * +http_response+ - A net/http response object.
    class Response

      attr_accessor :status_code, :headers, :body, :ok, :json

      def initialize(http_response)
        @status_code = http_response.code
        @headers = http_response.to_hash
        @body = http_response.body
        @ok = http_response.kind_of? Net::HTTPSuccess

        begin
          @json = JSON.parse(http_response.body)
        rescue JSON::JSONError
          @json = nil
        end
      end
    end

    # TeleSign RestClient, useful for making generic RESTful requests against the API.
    #
    # * +customer_id+ - Your customer_id string associated with your account.
    # * +api_key+ - Your api_key string associated with your account.
    # * +rest_endpoint+ - (optional) Override the default rest_endpoint to target another endpoint.
    # * +timeout+ - (optional) How long to wait for the server to send data before giving up, as a float.
    def initialize(customer_id,
                   api_key,
                   rest_endpoint: 'https://rest-api.telesign.com',
                   proxy: nil,
                   timeout: 10,
                   source: 'ruby_telesign',
                   sdk_version_origin: Telesign::SDK_VERSION,
                   sdk_version_dependency: nil)

      @customer_id = customer_id
      @api_key = api_key
      @rest_endpoint = rest_endpoint

      @user_agent = "TeleSignSDK/ruby Ruby/#{RUBY_VERSION} net:http:persistent/#{Net::HTTP::VERSION} OriginatingSDK/#{source} SDKVersion/#{sdk_version_origin}"

      if (source != 'ruby_telesign' && !sdk_version_dependency.nil?)
        @user_agent += " DependencySDKVersion/#{sdk_version_dependency}"
      end

      @http = Net::HTTP::Persistent.new(name: 'telesign', proxy: proxy)

      unless timeout.nil?
        @http.open_timeout = timeout
        @http.read_timeout = timeout
      end
    end

    # Generates the TeleSign REST API headers used to authenticate requests.
    #
    # Creates the canonicalized string_to_sign and generates the HMAC signature. This is used to authenticate requests
    # against the TeleSign REST API.
    #
    # See https://developer.telesign.com/docs/authentication for detailed API documentation.
    #
    # * +customer_id+ - Your account customer_id.
    # * +api_key+ - Your account api_key.
    # * +method_name+ - The HTTP method name of the request as a upper case string, should be one of 'POST', 'GET',
    #   'PUT', 'PATCH' or 'DELETE'.
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +url_encoded_fields+ - HTTP body parameters to perform the HTTP request with, must be a urlencoded string.
    # * +date_rfc2616+ - The date and time of the request formatted in rfc 2616, as a string.
    # * +nonce+ - A unique cryptographic nonce for the request, as a string.
    # * +user_agent+ - (optional) User Agent associated with the request, as a string.
    def self.generate_telesign_headers(customer_id,
                                       api_key,
                                       method_name,
                                       resource,
                                       content_type,
                                       encoded_fields,
                                       date_rfc2616: nil,
                                       nonce: nil,
                                       user_agent: nil,
                                       auth_method: 'HMAC-SHA256')

      if date_rfc2616.nil?
        date_rfc2616 = Time.now.httpdate
      end

      if nonce.nil?
        nonce = SecureRandom.uuid
      end

      content_type = (%w[POST PUT PATCH].include? method_name) ? content_type : ''

      if auth_method == 'HMAC-SHA256'

        string_to_sign = "#{method_name}"

        string_to_sign << "\n#{content_type}"

        string_to_sign << "\n#{date_rfc2616}"

        string_to_sign << "\nx-ts-auth-method:#{auth_method}"

        string_to_sign << "\nx-ts-nonce:#{nonce}"

        if !content_type.empty? and !encoded_fields.empty?
          string_to_sign << "\n#{encoded_fields}"
        end

        string_to_sign << "\n#{resource}"

        digest = OpenSSL::Digest.new('sha256')
        key = Base64.decode64(api_key)

        signature = Base64.encode64(OpenSSL::HMAC.digest(digest, key, string_to_sign)).strip

        authorization = "TSA #{customer_id}:#{signature}"
      else
        credentials = Base64.strict_encode64("#{customer_id}:#{api_key}")
        
        authorization = "Basic #{credentials}"
      end

      headers = {
          'Authorization'=>authorization,
          'Date'=>date_rfc2616,
          'x-ts-auth-method'=>auth_method,
          'x-ts-nonce'=>nonce
      }

      unless user_agent.nil?
        headers['User-Agent'] = user_agent
      end

      if !content_type.empty?
        headers['Content-Type'] = content_type
      end

      headers

    end

    # Generic TeleSign REST API POST handler.
    #
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +params+ - Body params to perform the POST request with, as a hash.
    def post(resource, **params)

      execute(Net::HTTP::Post, 'POST', resource, **params)

    end

    # Generic TeleSign REST API GET handler.
    #
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +params+ - Body params to perform the GET request with, as a hash.
    def get(resource, **params)

      execute(Net::HTTP::Get, 'GET', resource, **params)

    end

    # Generic TeleSign REST API PUT handler.
    #
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +params+ - Body params to perform the PUT request with, as a hash.
    def put(resource, **params)

      execute(Net::HTTP::Put, 'PUT', resource, **params)

    end

    # Generic TeleSign REST API DELETE handler.
    #
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +params+ - Body params to perform the DELETE request with, as a hash.
    def delete(resource, **params)

      execute(Net::HTTP::Delete, 'DELETE', resource, **params)

    end

    # Generic Telesign REST API PATCH handler.
    #
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +auth_method+ - Method to auth.
    # * +params+ - Body params to perform the PATCH request with, as a hash.
    def patch(resource, auth_method: 'HMAC-SHA256', **params)

      execute(Net::HTTP::Patch, 'PATCH', resource, auth_method: auth_method, **params)
    
    end

    private
    # Generic TeleSign REST API request handler.
    #
    # * +method_function+ - The net/http request to perform the request.
    # * +method_name+ - The HTTP method name, as an upper case string.
    # * +resource+ - The partial resource URI to perform the request against, as a string.
    # * +params+ - Body params to perform the HTTP request with, as a hash.
    def execute(method_function, method_name, resource, auth_method: 'HMAC-SHA256', **params)

      resource_uri = URI.parse("#{@rest_endpoint}#{resource}")

      encoded_fields = ''
      if %w[POST PUT PATCH].include? method_name
        request = method_function.new(resource_uri.request_uri)
        if content_type == "application/x-www-form-urlencoded"
          unless params.empty?
            encoded_fields = URI.encode_www_form(params, Encoding::UTF_8)
            request.set_form_data(params)
          end
        else
          encoded_fields = params.to_json
          request.body = encoded_fields
          request.set_content_type("application/json")
        end
      else
        resource_uri.query = URI.encode_www_form(params, Encoding::UTF_8)
        request = method_function.new(resource_uri.request_uri)
      end

      headers = RestClient.generate_telesign_headers(@customer_id,
                                                     @api_key,
                                                     method_name,
                                                     resource,
                                                     content_type,
                                                     encoded_fields,
                                                     user_agent: @user_agent,
                                                     auth_method: auth_method)

      headers.each do |k, v|
        request[k] = v
      end

      http_response = @http.request(resource_uri, request)

      Response.new(http_response)
    end

    def content_type
      "application/x-www-form-urlencoded"
    end
  end
end