rforce 0.10 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c3a071a2857df828c3921bb9cd49fab785f959bac60cfd01065dde0313f59e6c
4
+ data.tar.gz: 05afbdbf418ec1d4e1a7c58a390e2fc44c7b04d1c374e6acbba058f3e0a8863e
5
+ SHA512:
6
+ metadata.gz: 55baa254981ef2dbcd6c0d301579905beea00e2f0b4e1be0ac1eb0a311a805ed1409878ee0eef9d877a7cce307819cb1cbaf6aea9d8d55331d17fff3ad3b15b5
7
+ data.tar.gz: 4d2053a4e2c77d7da535fd770a779b5e313b0e753759f80e63128d94198c0095e289eeffd44c72ac23daff71e19a4f6438f45b46645219adb4eab39ce10e0a61
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.0
7
+ before_install: gem install bundler -v 2.0.2
8
+ install: "bundle install"
9
+ script: "bundle exec rake spec"
10
+ rvm:
11
+ - 2.3
12
+ - 2.4
13
+ - 2.5
14
+ - 2.6
15
+ - jruby
@@ -1,3 +1,38 @@
1
+ == 0.15.0 2021-04-22
2
+
3
+ * 1 security update
4
+ * Upgraded away from vulnerable OAuth version
5
+ * 1 compatibility update
6
+ * Dropped expat/xmlbuilder for lack of compatibility
7
+
8
+ == 0.14.0 2019-08-20
9
+
10
+ * 1 security update
11
+ * Upgraded away from vulnerable Nokogiri version
12
+ * 1 enhancement
13
+ * Slightly modernized project layout
14
+
15
+ == 0.13.0 2015-01-05
16
+ * 1 bug fix
17
+ * Attempt server connection even if caller hasn't logged in yet
18
+ (reported by desheikh)
19
+
20
+ == 0.12.0 2014-07-27
21
+ * 2 enhancements
22
+ * Clearer errors when skipping login (Victor Stan)
23
+ * Allow passing in an optional logger (Jeff Jolma)
24
+ * 3 bug fixes
25
+ * Fixed URL error in OAuth flow (reported by karthickbabuos)
26
+ * Corrected UTF-8 handling error (Maxime Rety)
27
+ * Fixed repeat login issue (Jeff Jolma)
28
+
29
+ == 0.11.0 2013-06-08
30
+ * 1 minor enhancement
31
+ * Give the README an .rdoc extension for easy Github viewing
32
+ (Brad Dunn)
33
+ * 1 bug fix
34
+ * Fixed malformed request (Jeff Jolma)
35
+
1
36
  == 0.10.0 2012-09-12
2
37
  * 1 bug fix
3
38
  * Integer auto-parsing mangles ZIP codes (reported by Jason
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at erin.dees@stitchfix.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rforce.gemspec
4
+ gemspec
data/Gemfile.ci ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'builder', '~> 3.0'
4
+ gem 'oauth', '~> 0.5.5'
5
+ gem 'rspec', '~> 3.0'
6
+ gem 'nokogiri', '~> 1.10.4'
@@ -1,14 +1,13 @@
1
1
  = rforce
2
2
 
3
- * http://rforce.rubyforge.org
4
- * http://rubyforge.org/projects/rforce
5
- * http://bitbucket.org/undees/rforce
6
3
  * http://github.com/undees/rforce
7
4
 
8
5
  == DESCRIPTION:
9
6
 
10
7
  RForce is a simple, usable binding to the Salesforce API.
11
8
 
9
+ {<img src="https://travis-ci.org/undees/rforce.png" />}[https://travis-ci.org/undees/rforce]
10
+
12
11
  == FEATURES:
13
12
 
14
13
  Rather than enforcing adherence to the sforce.com schema, RForce assumes you are familiar with the API. Ruby method names become SOAP method names. Nested Ruby hashes become nested XML elements.
@@ -73,7 +72,7 @@ Rather than enforcing adherence to the sforce.com schema, RForce assumes you are
73
72
 
74
73
  == LICENSE:
75
74
 
76
- Copyright (c) 2005-2012 Ian Dees and contributors
75
+ Copyright (c) 2005-2019 Erin Dees and contributors
77
76
 
78
77
  Permission is hereby granted, free of charge, to any person obtaining
79
78
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,32 +1,8 @@
1
- # -*- ruby -*-
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
2
3
 
3
- $:.unshift './lib'
4
+ RSpec::Core::RakeTask.new(:spec)
4
5
 
5
- require 'hoe'
6
- require 'hoe/gemspec2'
6
+ task :default => :spec
7
7
 
8
- Hoe.plugin :gemspec2
9
-
10
- Hoe.spec 'rforce' do
11
- developer('Ian Dees', 'undees@gmail.com')
12
-
13
- self.extra_deps << ['builder', '~> 3.0']
14
- self.extra_deps << ['oauth', '~> 0.4']
15
-
16
- self.extra_dev_deps << ['rspec', '~> 2.8']
17
- self.extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
18
- self.extra_dev_deps << ['hpricot', '~> 0.8']
19
- self.extra_dev_deps << ['nokogiri', '~> 1.5']
20
- self.extra_dev_deps << ['xmlparser', '~> 0.7']
21
-
22
- self.rdoc_locations = ['undees@rforce.rubyforge.org:/var/www/gforge-projects/rforce']
23
- self.remote_rdoc_dir = ''
24
-
25
- self.rspec_options = ['-rubygems', '--options', 'spec/spec.opts']
26
- end
27
-
28
- task :test => :spec
29
-
30
- Dir['tasks/**/*.rake'].each { |rake| load rake }
31
-
32
- # vim: syntax=Ruby
8
+ Dir['lib/tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,26 @@
1
+ require 'oauth'
2
+
3
+ consumer_key = ENV['SALESFORCE_CONSUMER_KEY']
4
+ consumer_secret = ENV['SALESFORCE_CONSUMER_SECRET']
5
+
6
+ oauth_options = {
7
+ :site => 'https://login.salesforce.com',
8
+ :scheme => :body,
9
+ :request_token_path => '/_nc_external/system/security/oauth/RequestTokenHandler',
10
+ :authorize_path => '/setup/secur/RemoteAccessAuthorizationPage.apexp',
11
+ :access_token_path => '/_nc_external/system/security/oauth/AccessTokenHandler',
12
+ }
13
+
14
+ consumer = OAuth::Consumer.new consumer_key, consumer_secret, oauth_options
15
+ # consumer.http.set_debug_output STDERR # if you're curious
16
+
17
+ request = consumer.get_request_token
18
+ authorize_url = request.authorize_url :oauth_consumer_key => consumer_key
19
+
20
+ puts "Go to #{authorize_url} in your browser, then enter the verification code:"
21
+ verification_code = gets.strip
22
+
23
+ access = request.get_access_token :oauth_verifier => verification_code
24
+
25
+ puts "Access Token: " + access.token
26
+ puts "Access Secret: " + access.secret
@@ -0,0 +1,25 @@
1
+ require 'rforce'
2
+
3
+ oauth = {
4
+ :consumer_key => ENV['SALESFORCE_CONSUMER_KEY'],
5
+ :consumer_secret => ENV['SALESFORCE_CONSUMER_SECRET'],
6
+ :access_token => ENV['SALESFORCE_ACCESS_TOKEN'],
7
+ :access_secret => ENV['SALESFORCE_ACCESS_SECRET'],
8
+ :login_url => 'https://login.salesforce.com/services/OAuth/u/20.0'
9
+ }
10
+
11
+ binding = RForce::Binding.new \
12
+ 'https://www.salesforce.com/services/Soap/u/20.0',
13
+ nil,
14
+ oauth
15
+
16
+ binding.login_with_oauth
17
+
18
+ answer = binding.search \
19
+ :searchString =>
20
+ 'find {McFakerson Co} in name fields returning account(id)'
21
+
22
+ account = answer.searchResponse.result.searchRecords.record
23
+ account_id = account.Id
24
+
25
+ puts account_id
@@ -0,0 +1,20 @@
1
+ require 'rforce'
2
+
3
+ email = ENV['SALESFORCE_USER']
4
+ password = ENV['SALESFORCE_PASS']
5
+ token = ENV['SALESFORCE_TOKEN']
6
+
7
+ binding = RForce::Binding.new \
8
+ 'https://www.salesforce.com/services/Soap/u/20.0'
9
+
10
+ binding.login \
11
+ email, password + token
12
+
13
+ answer = binding.search \
14
+ :searchString =>
15
+ 'find {McFakerson Co} in name fields returning account(id)'
16
+
17
+ account = answer.searchResponse.result.searchRecords.record
18
+ account_id = account.Id
19
+
20
+ puts account_id
data/lib/rforce.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright (c) 2005-2010 Ian Dees and contributors
2
+ Copyright (c) 2005-2019 Erin Dees and contributors
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
@@ -53,8 +53,8 @@ module RForce
53
53
  # Expand Ruby data structures into XML.
54
54
  def expand(builder, args, xmlns = nil)
55
55
  # Nest arrays: [:a, 1, :b, 2] => [[:a, 1], [:b, 2]]
56
- if (args.class == Array)
57
- args.each_index{|i| args[i, 2] = [args[i, 2]]}
56
+ if args.is_a?(Array)
57
+ args = args.each_slice(2).to_a
58
58
  end
59
59
 
60
60
  args.each do |key, value|
@@ -44,99 +44,87 @@ module RForce
44
44
  MruHeader = '<partner:MruHeader soap:mustUnderstand="1"><partner:updateMru>true</partner:updateMru></partner:MruHeader>'
45
45
  ClientIdHeader = '<partner:CallOptions soap:mustUnderstand="1"><partner:client>%s</partner:client></partner:CallOptions>'
46
46
 
47
- # Connect to the server securely. If you pass an oauth hash, it
48
- # must contain the keys :consumer_key, :consumer_secret,
47
+ # Create a binding to the server (after which you can call login
48
+ # or login_with_oauth to connect to it). If you pass an oauth
49
+ # hash, it must contain the keys :consumer_key, :consumer_secret,
49
50
  # :access_token, :access_secret, and :login_url.
50
51
  #
51
52
  # proxy may be a URL of the form http://user:pass@example.com:port
52
53
  #
53
- def initialize(url, sid = nil, oauth = nil, proxy = nil)
54
+ # if a logger is specified, it will be used for very verbose SOAP logging
55
+ #
56
+ def initialize(url, sid = nil, oauth = nil, proxy = nil, logger = nil)
54
57
  @session_id = sid
55
58
  @oauth = oauth
56
59
  @proxy = proxy
57
60
  @batch_size = DEFAULT_BATCH_SIZE
58
-
59
- init_server(url)
61
+ @logger = logger
62
+ @url = URI.parse(url)
60
63
  end
61
64
 
62
-
63
65
  def show_debug
64
66
  ENV['SHOWSOAP'] == 'true'
65
67
  end
66
68
 
69
+ def create_server(url)
70
+ server = Net::HTTP.Proxy(@proxy).new(url.host, url.port)
71
+ server.use_ssl = (url.scheme == 'https')
72
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
67
73
 
68
- def init_server(url)
69
- @url = URI.parse(url)
70
-
71
- if (@oauth)
72
- consumer = OAuth::Consumer.new \
73
- @oauth[:consumer_key],
74
- @oauth[:consumer_secret],
75
- {
76
- :site => url,
77
- :proxy => @proxy
78
- }
79
-
80
- consumer.http.set_debug_output $stderr if show_debug
81
-
82
- @server = OAuth::AccessToken.new \
83
- consumer,
84
- @oauth[:access_token],
85
- @oauth[:access_secret]
86
-
87
- class << @server
88
- alias_method :post2, :post
89
- end
90
- else
91
- @server = Net::HTTP.Proxy(@proxy).new(@url.host, @url.port)
92
- @server.use_ssl = @url.scheme == 'https'
93
- @server.verify_mode = OpenSSL::SSL::VERIFY_NONE
94
-
95
- # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
96
- @server.set_debug_output $stderr if show_debug
97
- end
98
- end
99
-
100
- # Connect to remote server
101
- #
102
- def connect(user, password)
103
- @user = user
104
- @password = password
74
+ # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
75
+ server.set_debug_output $stderr if show_debug
105
76
 
106
- call_remote(:login, [:username, user, :password, password])
77
+ return server
107
78
  end
108
79
 
109
80
  # Log in to the server with a user name and password, remembering
110
81
  # the session ID returned to us by Salesforce.
111
82
  def login(user, password)
112
- response = connect(user, password)
83
+ @user = user
84
+ @password = password
85
+ @server = create_server(@url)
86
+ response = call_remote(:login, [:username, user, :password, password])
113
87
 
114
88
  raise "Incorrect user name / password [#{response.Fault}]" unless response.loginResponse
115
89
 
116
- result = response[:loginResponse][:result]
90
+ result = response[:loginResponse][:result]
117
91
  @session_id = result[:sessionId]
92
+ @url = URI.parse(result[:serverUrl])
93
+ @server = create_server(@url)
118
94
 
119
- init_server(result[:serverUrl])
120
-
121
- response
95
+ return response
122
96
  end
123
97
 
124
98
  # Log in to the server with OAuth, remembering
125
99
  # the session ID returned to us by Salesforce.
126
100
  def login_with_oauth
127
- result = @server.post \
128
- @oauth[:login_url],
101
+ consumer = OAuth::Consumer.new \
102
+ @oauth[:consumer_key],
103
+ @oauth[:consumer_secret]
104
+
105
+ access = OAuth::AccessToken.new \
106
+ consumer, @oauth[:access_token],
107
+ @oauth[:access_secret]
108
+
109
+ login_url = @oauth[:login_url]
110
+
111
+ result = access.post \
112
+ login_url,
129
113
  '',
130
114
  {'content-type' => 'application/x-www-form-urlencoded'}
131
115
 
132
116
  case result
133
117
  when Net::HTTPSuccess
134
- doc = REXML::Document.new result.body
118
+ doc = REXML::Document.new result.body
135
119
  @session_id = doc.elements['*/sessionId'].text
136
- server_url = doc.elements['*/serverUrl'].text
137
- init_server server_url
120
+ @url = URI.parse(doc.elements['*/serverUrl'].text)
121
+ @server = access
122
+
123
+ class << @server
124
+ alias_method :post2, :post
125
+ end
138
126
 
139
- return {:sessionId => @sessionId, :serverUrl => server_url}
127
+ return {:sessionId => @session_id, :serverUrl => @url.to_s}
140
128
  when Net::HTTPUnauthorized
141
129
  raise 'Invalid OAuth tokens'
142
130
  else
@@ -148,8 +136,13 @@ module RForce
148
136
  # a hash or (if order is important) an array of alternating
149
137
  # keys and values.
150
138
  def call_remote(method, args)
139
+ @server ||= create_server(@url)
151
140
 
152
- urn, soap_url = block_given? ? yield : ["urn:partner.soap.sforce.com", @url.path]
141
+ # Different URI requirements for regular vs. OAuth. This is
142
+ # *screaming* for a refactor.
143
+ fallback_soap_url = @oauth ? @url.to_s : @url.path
144
+
145
+ urn, soap_url = block_given? ? yield : ["urn:partner.soap.sforce.com", fallback_soap_url]
153
146
 
154
147
  # Create XML text from the arguments.
155
148
  expanded = ''
@@ -157,12 +150,12 @@ module RForce
157
150
  expand(@builder, {method => args}, urn)
158
151
 
159
152
  extra_headers = ""
160
-
153
+
161
154
  # QueryOptions is not valid when making an Apex Webservice SOAP call
162
155
  if !block_given?
163
156
  extra_headers << (QueryOptions % @batch_size)
164
157
  end
165
-
158
+
166
159
  extra_headers << (AssignmentRuleHeaderUsingRuleId % assignment_rule_id) if assignment_rule_id
167
160
  extra_headers << AssignmentRuleHeaderUsingDefaultRule if use_default_rule
168
161
  extra_headers << MruHeader if update_mru
@@ -181,6 +174,7 @@ module RForce
181
174
  # Fill in the blanks of the SOAP envelope with our
182
175
  # session ID and the expanded XML of our request.
183
176
  request = (Envelope % [@session_id, extra_headers, expanded])
177
+ @logger && @logger.info("RForce request: #{request}")
184
178
 
185
179
  # reset the batch size for the next request
186
180
  @batch_size = DEFAULT_BATCH_SIZE
@@ -201,29 +195,42 @@ module RForce
201
195
  end
202
196
 
203
197
  # Send the request to the server and read the response.
198
+ @logger && @logger.info("RForce request to host #{@server} url #{soap_url} headers: #{headers}")
204
199
  response = @server.post2(soap_url, request.lstrip, headers)
205
200
 
206
201
  # decode if we have encoding
207
202
  content = decode(response)
208
203
 
204
+ # Fix charset encoding. Needed because the "content" variable may contain a UTF-8
205
+ # or ISO-8859-1 string, but is carrying the US-ASCII encoding.
206
+ content = fix_encoding(content)
207
+
209
208
  # Check to see if INVALID_SESSION_ID was raised and try to relogin in
210
209
  if method != :login and @session_id and content =~ /sf:INVALID_SESSION_ID/
211
- login(@user, @password)
210
+ if @user
211
+ login(@user, @password)
212
+ else
213
+ raise "INVALID_SESSION_ID"
214
+ end
212
215
 
213
216
  # repackage and rencode request with the new session id
214
- request = (Envelope % [@session_id, @batch_size, extra_headers, expanded])
217
+ request = (Envelope % [@session_id, extra_headers, expanded])
215
218
  request = encode(request)
216
219
 
217
220
  # Send the request to the server and read the response.
218
221
  response = @server.post2(soap_url, request.lstrip, headers)
219
222
 
220
223
  content = decode(response)
224
+
225
+ # Fix charset encoding. Needed because the "content" variable may contain a UTF-8
226
+ # or ISO-8859-1 string, but is carrying the US-ASCII encoding.
227
+ content = fix_encoding(content)
221
228
  end
222
229
 
230
+ @logger && @logger.info("RForce response: #{content}")
223
231
  SoapResponse.new(content).parse
224
232
  end
225
233
 
226
-
227
234
  # decode gzip
228
235
  def decode(response)
229
236
  encoding = response['Content-Encoding']
@@ -246,7 +253,6 @@ module RForce
246
253
  end
247
254
  end
248
255
 
249
-
250
256
  # encode gzip
251
257
  def encode(request)
252
258
  return request if show_debug
@@ -261,6 +267,28 @@ module RForce
261
267
  end
262
268
  end
263
269
 
270
+ # fix invalid US-ASCII strings by applying the correct encoding on ruby 1.9+
271
+ def fix_encoding(string)
272
+ if [:valid_encoding?, :force_encoding].all? { |m| string.respond_to?(m) }
273
+ if !string.valid_encoding?
274
+ # The 2 possible encodings in responses are UTF-8 and ISO-8859-1
275
+ # http://www.salesforce.com/us/developer/docs/api/Content/implementation_considerations.htm#topic-title_international
276
+ #
277
+ ["UTF-8", "ISO-8859-1"].each do |encoding_name|
278
+
279
+ s = string.dup.force_encoding(encoding_name)
280
+
281
+ if s.valid_encoding?
282
+ return s
283
+ end
284
+ end
285
+
286
+ raise "Invalid encoding in SOAP response: not in [US-ASCII, UTF-8, ISO-8859-1]"
287
+ end
288
+ end
289
+
290
+ return string
291
+ end
264
292
 
265
293
  # Turns method calls on this object into remote SOAP calls.
266
294
  def method_missing(method, *args)