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 +13 -26
- data/lib/restforce/client/api.rb +71 -26
- data/lib/restforce/client/authentication.rb +1 -1
- data/lib/restforce/client/canvas.rb +2 -7
- data/lib/restforce/signed_request.rb +48 -0
- data/lib/restforce/version.rb +1 -1
- data/spec/lib/client_spec.rb +22 -30
- data/spec/lib/signed_request_spec.rb +24 -0
- metadata +5 -2
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
|
-
|
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
|
data/lib/restforce/client/api.rb
CHANGED
@@ -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.
|
126
|
-
# something bad happens
|
127
|
-
def create(
|
128
|
-
create!(
|
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
|
-
#
|
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.
|
137
|
-
#
|
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
|
151
|
-
|
152
|
-
|
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
|
-
#
|
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
|
160
|
-
#
|
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
|
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(
|
183
|
-
upsert!(
|
206
|
+
def upsert(*args)
|
207
|
+
upsert!(*args)
|
184
208
|
rescue *exceptions
|
185
209
|
false
|
186
210
|
end
|
187
211
|
|
188
|
-
#
|
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
|
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.
|
240
|
+
# client.destroy('Account', '0016000000MRatd')
|
205
241
|
#
|
206
|
-
# Returns true if the sobject was successfully deleted
|
207
|
-
|
208
|
-
|
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
|
-
#
|
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
|
216
|
-
#
|
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
|
-
|
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
|
-
|
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
|
data/lib/restforce/version.rb
CHANGED
data/spec/lib/client_spec.rb
CHANGED
@@ -265,19 +265,31 @@ shared_examples_for 'methods' do
|
|
265
265
|
end
|
266
266
|
|
267
267
|
describe '.authenticate!' do
|
268
|
-
|
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
|
-
|
275
|
-
|
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
|
-
|
279
|
-
|
280
|
-
|
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.
|
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:
|
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
|