restforce 1.0.1 → 1.0.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/lib/restforce.rb CHANGED
@@ -2,20 +2,21 @@ require 'faraday'
2
2
  require 'faraday_middleware'
3
3
  require 'json'
4
4
 
5
- require 'openssl'
6
- require 'base64'
7
-
8
5
  require 'restforce/version'
9
6
  require 'restforce/config'
10
- require 'restforce/mash'
11
- require 'restforce/collection'
12
- require 'restforce/sobject'
13
- require 'restforce/upload_io'
14
- require 'restforce/client'
15
-
16
- require 'restforce/middleware'
17
7
 
18
8
  module Restforce
9
+ autoload :SignedRequest, 'restforce/signed_request'
10
+ autoload :Collection, 'restforce/collection'
11
+ autoload :Middleware, 'restforce/middleware'
12
+ autoload :UploadIO, 'restforce/upload_io'
13
+ autoload :SObject, 'restforce/sobject'
14
+ autoload :Client, 'restforce/client'
15
+ autoload :Mash, 'restforce/mash'
16
+
17
+ AuthenticationError = Class.new(StandardError)
18
+ UnauthorizedError = Class.new(StandardError)
19
+
19
20
  class << self
20
21
  # Alias for Restforce::Client.new
21
22
  #
@@ -24,25 +25,11 @@ module Restforce
24
25
  Restforce::Client.new(options)
25
26
  end
26
27
 
27
- # Public: Decodes a signed request received from Force.com Canvas.
28
- #
29
- # message - The POST message containing the signed request from Salesforce.
30
- # client_secret - The oauth client secret used to encrypt the message.
31
- #
32
- # Returns the Hash context if the message is valid.
33
- def decode_signed_request(message, client_secret)
34
- signature, payload = message.split('.')
35
- signature = Base64.decode64(signature)
36
- digest = OpenSSL::Digest::Digest.new('sha256')
37
- hmac = OpenSSL::HMAC.digest(digest, client_secret, payload)
38
- return nil if signature != hmac
39
- JSON.parse(Base64.decode64(payload))
28
+ def decode_signed_request(signed_request, client_secret)
29
+ SignedRequest.decode(signed_request, client_secret)
40
30
  end
41
31
  end
42
32
 
43
- class AuthenticationError < StandardError; end
44
- class UnauthorizedError < StandardError; end
45
-
46
33
  # Add .tap method in Ruby 1.8
47
34
  module CoreExtensions
48
35
  def tap
@@ -57,7 +57,7 @@ module Restforce
57
57
  # # => { ... }
58
58
  #
59
59
  # Returns the Hash representation of the describe call.
60
- def describe(sobject=nil)
60
+ def describe(sobject = nil)
61
61
  if sobject
62
62
  api_get("sobjects/#{sobject.to_s}/describe").body
63
63
  else
@@ -116,25 +116,37 @@ module Restforce
116
116
 
117
117
  # Public: Insert a new record.
118
118
  #
119
+ # sobject - String name of the sobject.
120
+ # attrs - Hash of attributes to set on the new record.
121
+ #
119
122
  # Examples
120
123
  #
121
124
  # # Add a new account
122
125
  # client.create('Account', Name: 'Foobar Inc.')
123
126
  # # => '0016000000MRatd'
124
127
  #
125
- # Returns the String Id of the newly created sobject. Returns false if
126
- # something bad happens
127
- def create(sobject, attrs)
128
- create!(sobject, attrs)
128
+ # Returns the String Id of the newly created sobject.
129
+ # Returns false if something bad happens.
130
+ def create(*args)
131
+ create!(*args)
129
132
  rescue *exceptions
130
133
  false
131
134
  end
132
135
  alias_method :insert, :create
133
136
 
134
- # See .create
137
+ # Public: Insert a new record.
138
+ #
139
+ # sobject - String name of the sobject.
140
+ # attrs - Hash of attributes to set on the new record.
141
+ #
142
+ # Examples
143
+ #
144
+ # # Add a new account
145
+ # client.create!('Account', Name: 'Foobar Inc.')
146
+ # # => '0016000000MRatd'
135
147
  #
136
- # Returns the String Id of the newly created sobject. Raises an error if
137
- # something bad happens.
148
+ # Returns the String Id of the newly created sobject.
149
+ # Raises exceptions if an error is returned from Salesforce.
138
150
  def create!(sobject, attrs)
139
151
  api_post("sobjects/#{sobject}", attrs).body['id']
140
152
  end
@@ -142,22 +154,34 @@ module Restforce
142
154
 
143
155
  # Public: Update a record.
144
156
  #
157
+ # sobject - String name of the sobject.
158
+ # attrs - Hash of attributes to set on the record.
159
+ #
145
160
  # Examples
146
161
  #
147
162
  # # Update the Account with Id '0016000000MRatd'
148
163
  # client.update('Account', Id: '0016000000MRatd', Name: 'Whizbang Corp')
149
164
  #
150
- # Returns true if the sobject was successfully updated, false otherwise.
151
- def update(sobject, attrs)
152
- update!(sobject, attrs)
165
+ # Returns true if the sobject was successfully updated.
166
+ # Returns false if there was an error.
167
+ def update(*args)
168
+ update!(*args)
153
169
  rescue *exceptions
154
170
  false
155
171
  end
156
172
 
157
- # See .update
173
+ # Public: Update a record.
174
+ #
175
+ # sobject - String name of the sobject.
176
+ # attrs - Hash of attributes to set on the record.
177
+ #
178
+ # Examples
179
+ #
180
+ # # Update the Account with Id '0016000000MRatd'
181
+ # client.update!('Account', Id: '0016000000MRatd', Name: 'Whizbang Corp')
158
182
  #
159
- # Returns true if the sobject was successfully updated, raises an error
160
- # otherwise.
183
+ # Returns true if the sobject was successfully updated.
184
+ # Raises an exception if an error is returned from Salesforce.
161
185
  def update!(sobject, attrs)
162
186
  id = attrs.delete(attrs.keys.find { |k| k.to_s.downcase == 'id' })
163
187
  raise 'Id field missing.' unless id
@@ -165,7 +189,7 @@ module Restforce
165
189
  true
166
190
  end
167
191
 
168
- # Public: Update or Create a record based on an external ID
192
+ # Public: Update or create a record based on an external ID
169
193
  #
170
194
  # sobject - The name of the sobject to created.
171
195
  # field - The name of the external Id field to match against.
@@ -179,17 +203,26 @@ module Restforce
179
203
  # Returns true if the record was found and updated.
180
204
  # Returns the Id of the newly created record if the record was created.
181
205
  # Returns false if something bad happens.
182
- def upsert(sobject, field, attrs)
183
- upsert!(sobject, field, attrs)
206
+ def upsert(*args)
207
+ upsert!(*args)
184
208
  rescue *exceptions
185
209
  false
186
210
  end
187
211
 
188
- # See .upsert
212
+ # Public: Update or create a record based on an external ID
213
+ #
214
+ # sobject - The name of the sobject to created.
215
+ # field - The name of the external Id field to match against.
216
+ # attrs - Hash of attributes for the record.
217
+ #
218
+ # Examples
219
+ #
220
+ # # Update the record with external ID of 12
221
+ # client.upsert!('Account', 'External__c', External__c: 12, Name: 'Foobar')
189
222
  #
190
223
  # Returns true if the record was found and updated.
191
224
  # Returns the Id of the newly created record if the record was created.
192
- # Raises an error if something bad happens.
225
+ # Raises an exception if an error is returned from Salesforce.
193
226
  def upsert!(sobject, field, attrs)
194
227
  external_id = attrs.delete(attrs.keys.find { |k| k.to_s.downcase == field.to_s.downcase })
195
228
  response = api_patch "sobjects/#{sobject}/#{field.to_s}/#{external_id}", attrs
@@ -198,22 +231,34 @@ module Restforce
198
231
 
199
232
  # Public: Delete a record.
200
233
  #
234
+ # sobject - String name of the sobject.
235
+ # id - The Salesforce ID of the record.
236
+ #
201
237
  # Examples
202
238
  #
203
239
  # # Delete the Account with Id '0016000000MRatd'
204
- # client.delete('Account', '0016000000MRatd')
240
+ # client.destroy('Account', '0016000000MRatd')
205
241
  #
206
- # Returns true if the sobject was successfully deleted, false otherwise.
207
- def destroy(sobject, id)
208
- destroy!(sobject, id)
242
+ # Returns true if the sobject was successfully deleted.
243
+ # Returns false if an error is returned from Salesforce.
244
+ def destroy(*args)
245
+ destroy!(*args)
209
246
  rescue *exceptions
210
247
  false
211
248
  end
212
249
 
213
- # See .destroy
250
+ # Public: Delete a record.
251
+ #
252
+ # sobject - String name of the sobject.
253
+ # id - The Salesforce ID of the record.
254
+ #
255
+ # Examples
256
+ #
257
+ # # Delete the Account with Id '0016000000MRatd'
258
+ # client.destroy('Account', '0016000000MRatd')
214
259
  #
215
- # Returns true of the sobject was successfully deleted, raises an error
216
- # otherwise.
260
+ # Returns true of the sobject was successfully deleted.
261
+ # Raises an exception if an error is returned from Salesforce.
217
262
  def destroy!(sobject, id)
218
263
  api_delete "sobjects/#{sobject}/#{id}"
219
264
  true
@@ -4,7 +4,7 @@ module Restforce
4
4
 
5
5
  # Public: Force an authentication
6
6
  def authenticate!
7
- raise 'No authentication middleware present' unless authentication_middleware
7
+ raise AuthenticationError, 'No authentication middleware present' unless authentication_middleware
8
8
  middleware = authentication_middleware.new nil, self, @options
9
9
  middleware.authenticate!
10
10
  end
@@ -2,14 +2,9 @@ module Restforce
2
2
  class Client
3
3
  module Canvas
4
4
 
5
- # Public: Decodes a signed request received from Force.com Canvas.
6
- #
7
- # message - The POST message containing the signed request from Salesforce.
8
- #
9
- # Returns the Hash context if the message is valid.
10
- def decode_signed_request(message)
5
+ def decode_signed_request(signed_request)
11
6
  raise 'client_secret not set' unless @options[:client_secret]
12
- Restforce.decode_signed_request(message, @options[:client_secret])
7
+ SignedRequestd.decode(signed_request, @options[:client_secret])
13
8
  end
14
9
 
15
10
  end
@@ -0,0 +1,48 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'json'
4
+
5
+ module Restforce
6
+ class SignedRequest
7
+ # Public: Initializes and decodes the signed request
8
+ #
9
+ # signed_request - The POST message containing the signed request from Salesforce.
10
+ # client_secret - The oauth client secret used to encrypt the signed request.
11
+ #
12
+ # Returns the parsed JSON context.
13
+ def self.decode(signed_request, client_secret)
14
+ new(signed_request, client_secret).decode
15
+ end
16
+
17
+ def initialize(signed_request, client_secret)
18
+ @client_secret = client_secret
19
+ split_components(signed_request)
20
+ end
21
+
22
+ # Public: Decode the signed request.
23
+ #
24
+ # Returns the parsed JSON context.
25
+ # Returns nil if the signed request is invalid.
26
+ def decode
27
+ return nil if signature != hmac
28
+ JSON.parse(Base64.decode64(payload))
29
+ end
30
+
31
+ private
32
+ attr_reader :client_secret, :signature, :payload
33
+
34
+ def split_components(signed_request)
35
+ @signature, @payload = signed_request.split('.')
36
+ @signature = Base64.decode64(@signature)
37
+ end
38
+
39
+ def hmac
40
+ OpenSSL::HMAC.digest(digest, client_secret, payload)
41
+ end
42
+
43
+ def digest
44
+ OpenSSL::Digest::Digest.new('sha256')
45
+ end
46
+
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = '1.0.1'
2
+ VERSION = '1.0.2'
3
3
  end
@@ -265,19 +265,31 @@ shared_examples_for 'methods' do
265
265
  end
266
266
 
267
267
  describe '.authenticate!' do
268
- before do
269
- @request = stub_login_request(:with_body => "grant_type=password&client_id=client_id&client_secret=" \
270
- "client_secret&username=foo&password=barsecurity_token").
271
- to_return(:status => 200, :body => fixture(:auth_success_response))
272
- end
268
+ subject { client.authenticate! }
273
269
 
274
- after do
275
- expect(@request).to have_been_requested
270
+ context 'when successful' do
271
+ before do
272
+ @request = stub_login_request(:with_body => "grant_type=password&client_id=client_id&client_secret=" \
273
+ "client_secret&username=foo&password=barsecurity_token").
274
+ to_return(:status => 200, :body => fixture(:auth_success_response))
275
+ end
276
+
277
+ after do
278
+ expect(@request).to have_been_requested
279
+ end
280
+
281
+ it { should be_a Hash }
276
282
  end
277
283
 
278
- subject { client.authenticate! }
279
- specify { expect { subject }.to_not raise_error }
280
- it { should be_a Hash }
284
+ context 'when no authentication middleware is present' do
285
+ before do
286
+ client.stub(:authentication_middleware).and_return(nil)
287
+ end
288
+
289
+ it 'should raise an exception' do
290
+ expect { subject }.to raise_error Restforce::AuthenticationError, 'No authentication middleware present'
291
+ end
292
+ end
281
293
  end
282
294
 
283
295
  describe '.cache' do
@@ -287,26 +299,6 @@ shared_examples_for 'methods' do
287
299
  it { should eq cache }
288
300
  end
289
301
 
290
- describe '.decode_signed_request' do
291
- subject { client.decode_signed_request(message) }
292
-
293
- context 'when the message is valid' do
294
- let(:data) { Base64.encode64('{ "key": "value" }') }
295
- let(:message) do
296
- digest = OpenSSL::Digest::Digest.new('sha256')
297
- signature = Base64.encode64(OpenSSL::HMAC.digest(digest, client_secret, data))
298
- "#{signature}.#{data}"
299
- end
300
-
301
- it { should eq('key' => 'value') }
302
- end
303
-
304
- context 'when the message is invalid' do
305
- let(:message) { 'foobar.awdkjkj' }
306
- it { should be_nil }
307
- end
308
- end
309
-
310
302
  describe '.middleware' do
311
303
  subject { client.middleware }
312
304
  it { should eq client.send(:connection).builder }
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Restforce::SignedRequest do
4
+ let(:client_secret) { 'foo' }
5
+ let(:message) do
6
+ digest = OpenSSL::Digest::Digest.new('sha256')
7
+ signature = Base64.encode64(OpenSSL::HMAC.digest(digest, client_secret, data))
8
+ "#{signature}.#{data}"
9
+ end
10
+
11
+ describe '.decode' do
12
+ subject { described_class.new(message, client_secret).decode }
13
+
14
+ context 'when the message is valid' do
15
+ let(:data) { Base64.encode64('{"key": "value"}') }
16
+ it { should eq('key' => 'value') }
17
+ end
18
+
19
+ context 'when the message is invalid' do
20
+ let(:message) { 'foobar.awdkjkj' }
21
+ it { should be_nil }
22
+ end
23
+ end
24
+ 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: 1.0.1
4
+ version: 1.0.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-12-31 00:00:00.000000000 Z
12
+ date: 2013-01-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -177,6 +177,7 @@ files:
177
177
  - lib/restforce/middleware/mashify.rb
178
178
  - lib/restforce/middleware/multipart.rb
179
179
  - lib/restforce/middleware/raise_error.rb
180
+ - lib/restforce/signed_request.rb
180
181
  - lib/restforce/sobject.rb
181
182
  - lib/restforce/upload_io.rb
182
183
  - lib/restforce/version.rb
@@ -225,6 +226,7 @@ files:
225
226
  - spec/lib/middleware/logger_spec.rb
226
227
  - spec/lib/middleware/mashify_spec.rb
227
228
  - spec/lib/middleware/raise_error_spec.rb
229
+ - spec/lib/signed_request_spec.rb
228
230
  - spec/lib/sobject_spec.rb
229
231
  - spec/spec_helper.rb
230
232
  - spec/support/basic_client.rb
@@ -299,6 +301,7 @@ test_files:
299
301
  - spec/lib/middleware/logger_spec.rb
300
302
  - spec/lib/middleware/mashify_spec.rb
301
303
  - spec/lib/middleware/raise_error_spec.rb
304
+ - spec/lib/signed_request_spec.rb
302
305
  - spec/lib/sobject_spec.rb
303
306
  - spec/spec_helper.rb
304
307
  - spec/support/basic_client.rb