restforce 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of restforce might be problematic. Click here for more details.

data/README.md CHANGED
@@ -197,8 +197,6 @@ _See also: http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_ups
197
197
 
198
198
  ### destroy(sobject, id)
199
199
 
200
- _Alias: delete_
201
-
202
200
  Takes an sobject name and an Id and deletes the record. Returns true if the
203
201
  record was successfully deleted.
204
202
 
@@ -231,6 +229,26 @@ _See also: http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_des
231
229
 
232
230
  * * *
233
231
 
232
+ ### authenticate!
233
+
234
+ Performs an authentication and returns the response. In general, calling this
235
+ directly shouldn't be required, since the client will handle authentication for
236
+ you automatically. This should only be used if you want to force
237
+ an authentication before using the streaming api, or you want to get some
238
+ information about the user.
239
+
240
+ ```ruby
241
+ response = client.authenticate!
242
+ # => #<Restforce::Mash access_token="..." id="https://login.salesforce.com/id/00DE0000000cOGcMAM/005E0000001eM4LIAU" instance_url="https://na9.salesforce.com" issued_at="1348465359751" scope="api refresh_token" signature="3fW0pC/TEY2cjK5FCBFOZdjRtCfAuEbK1U74H/eF+Ho=">
243
+
244
+ # Get the user information
245
+ info = client.get(response.id).body
246
+ info.user_id
247
+ # => '005E0000001eM4LIAU'
248
+ ```
249
+
250
+ * * *
251
+
234
252
  ### File Uploads
235
253
 
236
254
  Using the new [Blob Data](http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_sobject_insert_update_blob.htm) api feature (500mb limit):
data/lib/restforce.rb CHANGED
@@ -28,5 +28,4 @@ module Restforce
28
28
 
29
29
  class AuthenticationError < StandardError; end
30
30
  class UnauthorizedError < StandardError; end
31
- class InstanceURLError < StandardError; end
32
31
  end
@@ -243,7 +243,6 @@ module Restforce
243
243
  rescue *exceptions
244
244
  false
245
245
  end
246
- alias_method :delete, :destroy
247
246
 
248
247
  # See .destroy
249
248
  #
@@ -253,7 +252,6 @@ module Restforce
253
252
  api_delete "sobjects/#{sobject}/#{id}"
254
253
  true
255
254
  end
256
- alias_method :delete!, :destroy!
257
255
 
258
256
  # Public: Runs the block with caching disabled.
259
257
  #
@@ -279,10 +277,9 @@ module Restforce
279
277
 
280
278
  # Public: Force an authentication
281
279
  def authenticate!
282
- connection.headers['X-ForceAuthenticate'] = true
283
- get nil
284
- ensure
285
- connection.headers.delete('X-ForceAuthenticate')
280
+ raise 'No authentication middleware present' unless authentication_middleware
281
+ middleware = authentication_middleware.new nil, self, @options
282
+ middleware.authenticate!
286
283
  end
287
284
 
288
285
  # Public: Decodes a signed request received from Force.com Canvas.
@@ -322,11 +319,16 @@ module Restforce
322
319
  # Returns the Faraday::Response.
323
320
  [:get, :post, :put, :delete, :patch].each do |method|
324
321
  define_method method do |*args|
322
+ retries = @options[:authentication_retries]
325
323
  begin
326
324
  connection.send(method, *args)
327
- rescue Restforce::InstanceURLError
328
- connection.url_prefix = @options[:instance_url]
329
- connection.send(method, *args)
325
+ rescue Restforce::UnauthorizedError
326
+ if retries > 0
327
+ retries -= 1
328
+ connection.url_prefix = @options[:instance_url]
329
+ retry
330
+ end
331
+ raise
330
332
  end
331
333
  end
332
334
 
@@ -350,7 +352,7 @@ module Restforce
350
352
 
351
353
  # Internal: Internal faraday connection where all requests go through
352
354
  def connection
353
- @connection ||= Faraday.new do |builder|
355
+ @connection ||= Faraday.new(@options[:instance_url]) do |builder|
354
356
  builder.use Restforce::Middleware::Mashify, self, @options
355
357
  builder.use Restforce::Middleware::Multipart
356
358
  builder.request :json
@@ -360,6 +362,7 @@ module Restforce
360
362
  builder.use Restforce::Middleware::RaiseError
361
363
  builder.response :json
362
364
  builder.use Restforce::Middleware::Caching, cache, @options if cache
365
+ builder.use FaradayMiddleware::FollowRedirects
363
366
  builder.use Restforce::Middleware::Logger, Restforce.configuration.logger, @options if Restforce.log?
364
367
  builder.adapter Faraday.default_adapter
365
368
  end
@@ -7,40 +7,35 @@ module Restforce
7
7
  class Middleware::Authentication < Restforce::Middleware
8
8
 
9
9
  def call(env)
10
- retries = @options[:authentication_retries]
11
- request_body = env[:body]
12
- request = env[:request]
13
- begin
14
- return authenticate! if force_authenticate?(env)
15
- @app.call(env)
16
- rescue Restforce::UnauthorizedError
17
- if retries > 0
18
- authenticate!
19
- env[:body] = request_body
20
- env[:request] = request
21
- retries -= 1
22
- retry
23
- end
24
- raise
25
- end
10
+ @app.call(env)
11
+ rescue Restforce::UnauthorizedError
12
+ authenticate!
13
+ raise
26
14
  end
27
15
 
28
16
  def authenticate!
29
- raise 'must subclass'
17
+ response = connection.post '/services/oauth2/token' do |req|
18
+ req.body = URI.encode_www_form params
19
+ end
20
+ raise Restforce::AuthenticationError, error_message(response) if response.status != 200
21
+ @options[:instance_url] = response.body['instance_url']
22
+ @options[:oauth_token] = response.body['access_token']
23
+ response.body
24
+ end
25
+
26
+ def params
27
+ raise 'not implemented'
30
28
  end
31
29
 
32
30
  def connection
33
31
  @connection ||= Faraday.new(:url => "https://#{@options[:host]}") do |builder|
32
+ builder.use Restforce::Middleware::Mashify, nil, @options
34
33
  builder.response :json
35
34
  builder.use Restforce::Middleware::Logger, Restforce.configuration.logger, @options if Restforce.log?
36
35
  builder.adapter Faraday.default_adapter
37
36
  end
38
37
  end
39
38
 
40
- def force_authenticate?(env)
41
- env[:request_headers] && env[:request_headers]['X-ForceAuthenticate']
42
- end
43
-
44
39
  def error_message(response)
45
40
  "#{response.body['error']}: #{response.body['error_description']}"
46
41
  end
@@ -3,19 +3,12 @@ module Restforce
3
3
  # Authentication middleware used if username and password flow is used
4
4
  class Middleware::Authentication::Password < Restforce::Middleware::Authentication
5
5
 
6
- def authenticate!
7
- response = connection.post '/services/oauth2/token' do |req|
8
- req.body = URI.encode_www_form(
9
- :grant_type => 'password',
10
- :client_id => @options[:client_id],
11
- :client_secret => @options[:client_secret],
12
- :username => @options[:username],
13
- :password => password
14
- )
15
- end
16
- raise Restforce::AuthenticationError, error_message(response) if response.status != 200
17
- @options[:instance_url] = response.body['instance_url']
18
- @options[:oauth_token] = response.body['access_token']
6
+ def params
7
+ { :grant_type => 'password',
8
+ :client_id => @options[:client_id],
9
+ :client_secret => @options[:client_secret],
10
+ :username => @options[:username],
11
+ :password => password }
19
12
  end
20
13
 
21
14
  def password
@@ -3,18 +3,11 @@ module Restforce
3
3
  # Authentication middleware used if oauth_token and refresh_token are set
4
4
  class Middleware::Authentication::Token < Restforce::Middleware::Authentication
5
5
 
6
- def authenticate!
7
- response = connection.post '/services/oauth2/token' do |req|
8
- req.body = URI.encode_www_form(
9
- :grant_type => 'refresh_token',
10
- :refresh_token => @options[:refresh_token],
11
- :client_id => @options[:client_id],
12
- :client_secret => @options[:client_secret]
13
- )
14
- end
15
- raise Restforce::AuthenticationError, error_message(response) if response.status != 200
16
- @options[:instance_url] = response.body['instance_url']
17
- @options[:oauth_token] = response.body['access_token']
6
+ def params
7
+ { :grant_type => 'refresh_token',
8
+ :refresh_token => @options[:refresh_token],
9
+ :client_id => @options[:client_id],
10
+ :client_secret => @options[:client_secret] }
18
11
  end
19
12
 
20
13
  end
@@ -4,22 +4,13 @@ module Restforce
4
4
  class Middleware::InstanceURL < Restforce::Middleware
5
5
 
6
6
  def call(env)
7
- # If the instance url isn't set in options, raise a
8
- # Restforce::UnauthorizedError to trigger reauthentication.
9
- raise Restforce::UnauthorizedError, 'instance url not set' unless @options[:instance_url]
10
-
11
- # If the url_prefix for the connection doesn't match the instance_url
12
- # set in the options, we raise an error which gets caught outside of
13
- # middleware, where the url_prefix is then set before retrying the
14
- # request. It would be ideal if this could all be handled in
15
- # middleware...
16
- raise Restforce::InstanceURLError unless connection.url_prefix == instance_url
17
-
7
+ # If the connection url_prefix isn't set, we must not be authenticated.
8
+ raise Restforce::UnauthorizedError, 'Connection prefix not set' unless url_prefix_set?
18
9
  @app.call(env)
19
10
  end
20
11
 
21
- def instance_url
22
- URI.parse(@options[:instance_url])
12
+ def url_prefix_set?
13
+ !!(connection.url_prefix && connection.url_prefix.host)
23
14
  end
24
15
 
25
16
  end
@@ -18,10 +18,7 @@ module Restforce
18
18
  # account.Name = 'Foobar'
19
19
  # account.save
20
20
  def save
21
- # Remove 'attributes' and parent/child relationships. We only want to
22
- # persist the attributes on the sobject.
23
21
  ensure_id
24
- attrs = self.to_hash.reject { |key, _| key =~ /.*__r/ || key =~ /^attributes$/ }
25
22
  @client.update(sobject_type, attrs)
26
23
  end
27
24
 
@@ -36,6 +33,12 @@ module Restforce
36
33
  @client.destroy(sobject_type, self.Id)
37
34
  end
38
35
 
36
+ # Public: Returns a hash representation of this object with the attributes
37
+ # key and parent/child relationships removed.
38
+ def attrs
39
+ self.to_hash.reject { |key, _| key =~ /.*__r/ || key =~ /^attributes$/ }
40
+ end
41
+
39
42
  private
40
43
 
41
44
  def ensure_id
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -330,6 +330,7 @@ shared_examples_for 'methods' do
330
330
 
331
331
  subject { client.authenticate! }
332
332
  specify { expect { subject }.to_not raise_error }
333
+ it { should be_a Hash }
333
334
  end
334
335
 
335
336
  describe '.cache' do
@@ -395,6 +396,64 @@ shared_examples_for 'methods' do
395
396
  specify { expect { subject }.to_not raise_error }
396
397
  end
397
398
  end
399
+
400
+ describe 'authentication retries' do
401
+ context 'when retries reaches 0' do
402
+ before do
403
+ @auth_request = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object', status: 401, with: 'expired_session_response')
404
+ @query_request = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
405
+ .with(:body => "grant_type=password&client_id=client_id&client_secret=" \
406
+ "client_secret&username=foo&password=barsecurity_token")
407
+ .to_return(:status => 200, :body => fixture(:auth_success_response))
408
+ end
409
+
410
+ subject { client.query('SELECT some, fields FROM object') }
411
+ specify { expect { subject }.to raise_error Restforce::UnauthorizedError }
412
+ end
413
+ end
414
+
415
+ describe '.query with caching' do
416
+ class MockCache
417
+ def initialize
418
+ @storage = {}
419
+ end
420
+
421
+ def fetch(key, &block)
422
+ @storage[key] || begin
423
+ block.call.tap do |result|
424
+ @storage[key] = result
425
+ end
426
+ end
427
+ end
428
+ end
429
+
430
+ let(:cache) { MockCache.new }
431
+ let(:oauth_token) { 'foobar' }
432
+
433
+ before do
434
+ @requests = [].tap do |requests|
435
+ requests << stub_request(:get, /query\?q=SELECT%20some,%20fields%20FROM%20object/)
436
+ .with(headers: { 'Authorization' => "OAuth #{oauth_token}" })
437
+ .to_return(status: 401, body: fixture('expired_session_response'))
438
+
439
+ requests << stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
440
+ .with(:body => "grant_type=password&client_id=client_id&client_secret=" \
441
+ "client_secret&username=foo&password=barsecurity_token")
442
+ .to_return(status: 200, body: fixture(:auth_success_response))
443
+
444
+ requests << stub_request(:get, /query\?q=SELECT%20some,%20fields%20FROM%20object/)
445
+ .with(headers: { 'Authorization' => "OAuth 00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs" })
446
+ .to_return(status: 200, body: fixture('sobject/query_success_response'))
447
+ end
448
+ end
449
+
450
+ after do
451
+ @requests.each { |request| request.should have_been_requested }
452
+ end
453
+
454
+ subject { client.query('SELECT some, fields FROM object') }
455
+ it { should be_an Array }
456
+ end
398
457
  end
399
458
 
400
459
  describe 'with mashify middleware' do
@@ -11,7 +11,7 @@ describe Restforce::Middleware::Authentication do
11
11
  it 'raises an error' do
12
12
  expect {
13
13
  middleware.authenticate!
14
- }.to raise_error RuntimeError, 'must subclass'
14
+ }.to raise_error RuntimeError, 'not implemented'
15
15
  end
16
16
  end
17
17
 
@@ -33,21 +33,8 @@ describe Restforce::Middleware::Authentication do
33
33
  end
34
34
 
35
35
  it 'attempts to authenticate' do
36
- app.should_receive(:call).once { |env| env[:body] = 'bar'; env[:request] = 'foo'; raise Restforce::UnauthorizedError.new('something bad') }
36
+ app.should_receive(:call).once.and_raise(Restforce::UnauthorizedError.new('something bad'))
37
37
  middleware.should_receive(:authenticate!)
38
- app.should_receive(:call).with(body: 'foo', request: { proxy: nil }).once
39
- middleware.call(env)
40
- end
41
- end
42
-
43
- context 'when the retry limit is reached' do
44
- before do
45
- app.should_receive(:call).twice.and_raise(Restforce::UnauthorizedError)
46
- middleware.should_receive(:authenticate!)
47
- end
48
-
49
- let(:retries) { 1 }
50
- it 'should raise an exception' do
51
38
  expect { middleware.call(env) }.to raise_error Restforce::UnauthorizedError
52
39
  end
53
40
  end
@@ -79,21 +66,4 @@ describe Restforce::Middleware::Authentication do
79
66
  end
80
67
  end
81
68
  end
82
-
83
- describe '.force_authenticate?' do
84
- subject { middleware.force_authenticate?(env) }
85
-
86
- context 'without X-ForceAuthenticate header set' do
87
- it { should be_false }
88
- end
89
-
90
- context 'with X-ForceAuthenticate header set' do
91
- before do
92
- env[:request_headers] = {}
93
- env[:request_headers]['X-ForceAuthenticate'] = true
94
- end
95
-
96
- it { should be_true }
97
- end
98
- end
99
69
  end
@@ -3,13 +3,15 @@ require 'spec_helper'
3
3
  describe Restforce::Middleware::InstanceURL do
4
4
  let(:app) { double('app') }
5
5
  let(:client) { double('client') }
6
- let(:options) { { :instance_url => instance_url } }
6
+ let(:options) { { } }
7
7
  let(:middleware) { described_class.new app, client, options }
8
8
 
9
9
  context 'when the instance url is not set' do
10
- let(:instance_url) { nil }
10
+ before do
11
+ client.stub_chain(:connection, :url_prefix).and_return(URI.parse('http:/'))
12
+ end
11
13
 
12
- it 'raises an error' do
14
+ it 'raises an exception' do
13
15
  expect {
14
16
  middleware.call(nil)
15
17
  }.to raise_error Restforce::UnauthorizedError
@@ -17,32 +19,13 @@ describe Restforce::Middleware::InstanceURL do
17
19
  end
18
20
 
19
21
  context 'when the instance url is set' do
20
- let(:instance_url) { 'https://foo.bar' }
21
-
22
22
  before do
23
- client.stub_chain(:connection, :url_prefix).and_return URI.parse(url_prefix)
24
- end
25
-
26
- context 'and it does not match the connection url prefix' do
27
- let(:url_prefix) { 'https://whiz.bang' }
28
-
29
- it 'raises an error' do
30
- expect {
31
- middleware.call(nil)
32
- }.to raise_error Restforce::InstanceURLError
33
- end
23
+ client.stub_chain(:connection, :url_prefix).and_return(URI.parse('http://foobar.com/'))
34
24
  end
35
25
 
36
- context 'and it matches the connection url prefix' do
37
- let(:url_prefix) { 'https://foo.bar' }
38
-
39
- before do
40
- app.should_receive(:call)
41
- end
42
-
43
- it 'calls tne next middleware' do
44
- middleware.call(nil)
45
- end
26
+ it 'does not raise an exception' do
27
+ app.should_receive(:call).once
28
+ middleware.call(nil)
46
29
  end
47
30
  end
48
31
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restforce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-22 00:00:00.000000000 Z
12
+ date: 2012-09-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake