Class: Videojuicer::OAuth::RequestProxy

Inherits:
Object
  • Object
show all
Includes:
Configurable, Exceptions
Defined in:
lib/videojuicer/oauth/request_proxy.rb

Constant Summary collapse

EXCLUDED_BASE_STRING_PORTS =

Requests to the following ports will not include the port in the signature base string. See OAuth spec 1.0a section 9.1.2 for details.

[80, 443].freeze
TIMEOUT =

allow Net::HTTP requests to take up to 5 minutes

60 * 5

Instance Attribute Summary

Attributes included from Configurable

#local_config

Instance Method Summary collapse

Methods included from Configurable

#api_version, #config, #configure!, #consumer_key, #consumer_secret, #host, #port, #protocol, #scope, #seed_name, #token, #token_secret, #user_id

Constructor Details

#initialize(options = {}) ⇒ RequestProxy

Initializes a new RequestProxy object which can be used to make requests. Accepts all the same options as Videojuicer::configure! as well as: token - The OAuth token to use in requests made through this proxy. token_secret - The OAuth token secret to use when encrypting the request signature.



35
36
37
# File 'lib/videojuicer/oauth/request_proxy.rb', line 35

def initialize(options={})
  configure!(options)
end

Instance Method Details

#authified_query_string(method, path, params = {}) ⇒ Object

Authifies the given parameters and converts them into a query string.



201
202
203
# File 'lib/videojuicer/oauth/request_proxy.rb', line 201

def authified_query_string(method, path, params={})
  normalize_params(authify_params(method, path, params))
end

#authify_params(method, path, params) ⇒ Object

Takes a set of business parameters you want sent to the provider, and merges them with the proxy configuration to produce a set of parameters that will be accepted by the OAuth provider.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/videojuicer/oauth/request_proxy.rb', line 208

def authify_params(method, path, params)
  defaults = {
    :oauth_consumer_key=>consumer_key,
    :oauth_token=>token,
    :api_version=>api_version,
    :oauth_timestamp=>Time.now.to_i,
    :oauth_nonce=>rand(9999),
    :oauth_signature_method=>"HMAC-SHA1",
    :seed_name=>seed_name,
    :user_id=>user_id
  }
  defaults.delete_if {|k,v| (!v) or (v.to_s.empty?) }
  params = defaults.merge(params)
  params[:oauth_signature] = signature(method, path, params)
  return params
end

#delete(path, params = {}) ⇒ Object

Makes a DELETE request given path and params. The host will be ascertained from the configuration options.



53
# File 'lib/videojuicer/oauth/request_proxy.rb', line 53

def delete(path, params={}); make_request(:delete, host, port, path, params); end

#flatten_params(params, *hash_path) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/videojuicer/oauth/request_proxy.rb', line 262

def flatten_params(params, *hash_path)
  op = {}
  params.sort {|a,b| a.to_s<=>b.to_s}.each do |key, value|
    path = hash_path.dup
    path << key.to_s
    
    if value.is_a?(Hash)
      op.merge! flatten_params(value, *path)
    elsif value
      key_path = path.first + path[1..(path.length-1)].collect {|h| "[#{h}]"}.join("")
      op[key_path] = value
    end
  end
  return op
end

#get(path, params = {}) ⇒ Object

Makes a GET request given path and params. The host will be ascertained from the configuration options.



41
# File 'lib/videojuicer/oauth/request_proxy.rb', line 41

def get(path, params={}); make_request(:get, host, port, path, params); end

#handle_response(response, request) ⇒ Object

Handles an HTTPResponse object appropriately. Redirects are followed, error states raise errors and success responses are returned directly.



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
# File 'lib/videojuicer/oauth/request_proxy.rb', line 119

def handle_response(response, request)
  c = response.code.to_i
  case c
  when 200..399
    # Successful or redirected response
    response
  when 409
    # Not ready but still successful
    response
  when 415
    # Validation error
    response
  when 401
    # Authentication problem
    response_error Unauthenticated, request, response
  when 403
    # Attempted to perform a forbidden action
    response_error Forbidden, request, response
  when 404
    # Resource URL not valid
    response_error NoResource, request, response
  when 406
    # Excuse me WTF r u doin
    response_error NotAcceptable, request, response
  when 411
    # App-side server error where request is not properly constructed.
    response_error ContentLengthRequired, request, response
  when 500..600
    # Remote application failure
    response_error RemoteApplicationError, request, response
  else
    response_error UnhandledHTTPStatus, request, response
  end
end

#host_stub(_protocol = protocol, _host = host, _port = port) ⇒ Object



113
114
115
# File 'lib/videojuicer/oauth/request_proxy.rb', line 113

def host_stub(_protocol=protocol, _host=host, _port=port)
  "#{_protocol}://#{_host}:#{_port}"
end

#make_request(method, host, port, path, params = {}) ⇒ Object

Does the actual work of making a request. Returns a Net::HTTPResponse object.



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
# File 'lib/videojuicer/oauth/request_proxy.rb', line 56

def make_request(method, host, port, path, params={})
  # Strip the files from the parameters to determine what, from the whole bundle, needs signing
  signature_params, multipart_params = split_by_signature_eligibility(params)

  query_string = nil

  if multipart_params.any?
    # Sign the params and include the as multipart
    multipart_params = flatten_params(
      authify_params(method, path, signature_params).deep_merge(multipart_params)
    )
  else
    # Use the query string
    query_string = authified_query_string(method, path, signature_params)
  end

  uri = Addressable::URI.new(
    :scheme => protocol,
    :host   => host,
    :port   => port,
    :path   => path,
    :query  => query_string
  )

  klass = request_class_for_method(method)

  if multipart_params.any?
    request = klass::Multipart.new(
      uri.request_uri,
      Videojuicer::OAuth::Multipart.new(multipart_params)
    )
  else
    request = klass.new(uri.request_uri)

    case method
    when :post, :put
      # Send a content-length on POST and PUT to avoid an HTTP 411 response
      query_string           = uri.query.to_s
      request                = klass.new(uri.path)
      request.content_type   = 'application/x-www-form-urlencoded'
      request.content_length = query_string.length
      request.body           = query_string
    end
  end

  response = Net::HTTP.start(uri.host, uri.port) do |http|
    http.read_timeout = TIMEOUT
    http.request(request)
  end

  handle_response(response, request)
rescue EOFError => e
  raise "EOF error when accessing #{uri.inspect}"
rescue Errno::ECONNREFUSED => e
  raise "Could not connect to #{uri.inspect}"
end

#normalize_params(params, *hash_path) ⇒ Object

Returns a string representing a normalised parameter hash. Supports nesting for rails or merb-style object properties supplied as nested hashes. For instance, the key ‘bar inside :foo=>{:bar=>“baz”} will be named foo in the signature and in the eventual request object.



258
259
260
# File 'lib/videojuicer/oauth/request_proxy.rb', line 258

def normalize_params(params, *hash_path)
  flatten_params(params).sort {|a,b| a.to_s <=> b.to_s}.collect {|k, v| "#{CGI.rfc3986_escape(k)}=#{CGI.rfc3986_escape(v.to_s)}" }.join("&")
end

#post(path, params = {}) ⇒ Object

Makes a POST request given path and params. The host will be ascertained from the configuration options.



45
# File 'lib/videojuicer/oauth/request_proxy.rb', line 45

def post(path, params={}); make_request(:post, host, port, path, params); end

#put(path, params = {}) ⇒ Object

Makes a PUT request given path and params. The host will be ascertained from the configuration options.



49
# File 'lib/videojuicer/oauth/request_proxy.rb', line 49

def put(path, params={}); make_request(:put, host, port, path, params); end

#request_class_for_method(m, in_module = Net::HTTP) ⇒ Object

Returns the Net::HTTPRequest subclass needed to make a request for the given method.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/videojuicer/oauth/request_proxy.rb', line 279

def request_class_for_method(m, in_module=Net::HTTP)
  case (m.is_a?(Symbol) ? m : m.downcase.to_sym rescue :get)
  when :post
    in_module::Post
  when :put
    in_module::Put
  when :head
    in_module::Head
  when :delete
    in_module::Delete
  else
    in_module::Get
  end
end

#response_error(exception_klass, request, response) ⇒ Object

Handles the response as an error of the desired type.



155
156
157
158
159
160
161
162
163
164
# File 'lib/videojuicer/oauth/request_proxy.rb', line 155

def response_error(exception_klass, request, response)
  begin
    e = JSON.parse(response.body)
    e = e["error"]
    raise exception_klass, "#{e["message"]} || 'an error has occurred, vj-core did not provide sufficient data to handle it properly' \n #{(e["backtrace"] || []).join("\n")}"
  rescue JSON::ParserError
    raise exception_klass, "#{exception_klass.to_s} : Response code was #{response.code} for request: #{request.path}"
  end
  
end

#signature(method, path, params) ⇒ Object

Calculates and returns the encrypted signature for this proxy object and the given request properties.



227
228
229
230
231
# File 'lib/videojuicer/oauth/request_proxy.rb', line 227

def signature(method, path, params)
  base = signature_base_string(method, path, params)
  signature_octet = HMAC::SHA1.digest(signature_secret, base)
  signature_base64 = [signature_octet].pack('m').chomp.gsub(/\n/, '')
end

#signature_base_string(method, path, params) ⇒ Object

Returns the unencrypted signature base string for this proxy object and the given request properties.



240
241
242
# File 'lib/videojuicer/oauth/request_proxy.rb', line 240

def signature_base_string(method, path, params)
  s = [method.to_s.upcase, "#{protocol}://#{signature_base_string_host}#{path}", normalize_params(params)].collect {|e| CGI.rfc3986_escape(e)}.join("&")
end

#signature_base_string_hostObject



244
245
246
247
248
249
250
251
252
# File 'lib/videojuicer/oauth/request_proxy.rb', line 244

def signature_base_string_host
  if EXCLUDED_BASE_STRING_PORTS.include?(port.to_i)
    # Natural port. Ignore the port
    host
  else
    # Weird port. Expect a signature.
    "#{host}:#{port}"
  end
end

#signature_secretObject

Calculates and returns the signature secret to be used for this proxy object.



234
235
236
# File 'lib/videojuicer/oauth/request_proxy.rb', line 234

def signature_secret
  [consumer_secret, token_secret].collect {|e| CGI.rfc3986_escape(e.to_s)}.join("&")
end

#signed_url(method, path, params = {}) ⇒ Object



196
197
198
# File 'lib/videojuicer/oauth/request_proxy.rb', line 196

def signed_url(method, path, params={})
  "#{protocol}://#{host}:#{port}#{path}?#{authified_query_string(method, path, params)}"
end

#split_by_signature_eligibility(params, *hash_path) ⇒ Object

Splits a given parameter hash into two hashes - one containing all string and non-binary parameters, and one containing all file/binary parameters. This action is performed recursively so that:

params = {:user=>{:attributes=>{:file=>some_file, :name=>"user name"}}, :foo=>"bar"}
normal, multipart = split_multipart_params(params)
normal.inspect # => {:user=>{:attributes=>{:name=>"user name"}}, :foo=>"bar"}
multipart.inspect # => {:user=>{:attributes=>{:file=>some_file}}}


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/videojuicer/oauth/request_proxy.rb', line 173

def split_by_signature_eligibility(params, *hash_path)
  strings = {}
  files = {}
  params.each do |key, value|
    if value.is_a?(Hash)
      # Call recursively
      s, f = split_by_signature_eligibility(value, *(hash_path+[key]))
      strings = strings.deep_merge(s)
      files = files.deep_merge(f)
    else 
      # Insert it into files at the current key path if it is a binary,
      # and into strings if it is not.
      pwd = (value.respond_to?(:read))? files : strings
      hash_path.each do |component|
        pwd[component] ||= {}
        pwd = pwd[component]
      end
      pwd[key] = value
    end
  end
  return strings, files
end