rforce 0.10 → 0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)