savon 0.9.10 → 0.9.11

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.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## 0.9.11 (2012-06-06)
2
+
3
+ * Feature: [#264](https://github.com/rubiii/savon/pull/264) - Thanks to @hoverlover, Savon and Akami now support
4
+ signed messages through WSSE.
5
+
6
+ * Fix: [#275](https://github.com/rubiii/savon/pull/275) - Add namespaces to keys in both the SOAP body hash as well
7
+ as any keys specified in a :order! Array instead of having to define them manually.
8
+
9
+ * Fix: [#257](https://github.com/rubiii/savon/issues/257) - Add ability to accept and send multiple cookies.
10
+
11
+ * Improvement: [#277](https://github.com/rubiii/savon/pull/277) automatically namespace the SOAP input tag.
12
+ Here's an example from the pull request:
13
+
14
+ ``` ruby
15
+ client.request :authenticate
16
+ ```
17
+
18
+ Note the automatic namespace identifier on the authenticate element, as well as the proper namespace inclusion
19
+ in the document:
20
+
21
+ ``` xml
22
+ <env:Envelope
23
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
24
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
25
+ xmlns:tns="http://v1_0.ws.auth.order.example.com/"
26
+ xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
27
+
28
+ <tns:authenticate>
29
+ <tns:user>username</tns:user>
30
+ <tns:password>password</tns:password>
31
+ </tns:authenticate>
32
+ </env:Envelope>
33
+ ```
34
+
1
35
  ## 0.9.10 (2012-06-06)
2
36
 
3
37
  * Feature: [#289](https://github.com/rubiii/savon/pull/289) - Allow the SOAP envelope header to be set as a String.
data/README.md CHANGED
@@ -22,7 +22,7 @@ Introduction
22
22
  require "savon"
23
23
 
24
24
  # create a client for your SOAP service
25
- client = Savon::Client.new("http://service.example.com?wsdl")
25
+ client = Savon.client("http://service.example.com?wsdl")
26
26
 
27
27
  client.wsdl.soap_actions
28
28
  # => [:create_user, :get_user, :get_all_users]
data/lib/savon/client.rb CHANGED
@@ -83,6 +83,11 @@ module Savon
83
83
 
84
84
  response = SOAP::Request.new(config, http, soap).response
85
85
  set_cookie response.http.headers
86
+
87
+ if wsse.verify_response
88
+ WSSE::VerifySignature.new(response.http.body).verify!
89
+ end
90
+
86
91
  response
87
92
  end
88
93
 
@@ -96,7 +101,19 @@ module Savon
96
101
 
97
102
  # Passes a cookie from the last request +headers+ to the next one.
98
103
  def set_cookie(headers)
99
- http.headers["Cookie"] = headers["Set-Cookie"] if headers["Set-Cookie"]
104
+ if headers["Set-Cookie"]
105
+ @cookies ||= {}
106
+ #handle single or multiple Set-Cookie Headers as returned by Rack::Utils::HeaderHash in HTTPI
107
+ set_cookies = [headers["Set-Cookie"]].flatten
108
+ set_cookies.each do |set_cookie|
109
+ # use the cookie name as the key to the hash to allow for cookie updates and seperation
110
+ # set the value to name=value (for easy joining), stopping when we hit the Cookie options
111
+ @cookies[set_cookie.split('=').first] = set_cookie.split(';').first
112
+ end
113
+
114
+ http.headers["Cookie"] = @cookies.values.join(';')
115
+ end
116
+
100
117
  end
101
118
 
102
119
  # Expects an Array of +args+ and returns an Array containing the namespace (might be +nil+),
@@ -112,8 +129,6 @@ module Savon
112
129
  # Expects an Array of +args+ to preconfigure the system.
113
130
  def preconfigure(args)
114
131
  soap.endpoint = wsdl.endpoint
115
- soap.namespace_identifier = args[0]
116
- soap.namespace = wsdl.namespace
117
132
  soap.element_form_default = wsdl.element_form_default
118
133
 
119
134
  body = args[2].delete(:body)
@@ -129,6 +144,18 @@ module Savon
129
144
 
130
145
  soap_action = args[2].delete(:soap_action) || args[1]
131
146
  set_soap_action soap_action
147
+
148
+ if wsdl.document? && (operation = wsdl.operations[args[1]]) && operation[:namespace_identifier]
149
+ soap.namespace_identifier = operation[:namespace_identifier].to_sym
150
+ soap.namespace = wsdl.parser.namespaces[soap.namespace_identifier.to_s]
151
+
152
+ # Override nil namespace with one specified in WSDL
153
+ args[0] = soap.namespace_identifier unless args[0]
154
+ else
155
+ soap.namespace_identifier = args[0]
156
+ soap.namespace = wsdl.namespace
157
+ end
158
+
132
159
  set_soap_input *args
133
160
  end
134
161
 
@@ -41,7 +41,20 @@ module Savon
41
41
  # Configures a given +http+ from the +soap+ object.
42
42
  def configure(http)
43
43
  http.url = soap.endpoint
44
- http.body = soap.to_xml
44
+
45
+ if soap.signature?
46
+ # First generate the document so that Signature can digest sections
47
+ soap.wsse.signature.document = soap.to_xml(true)
48
+
49
+ # Then re-generate the document so that Signature can sign the digest
50
+ soap.wsse.signature.document = soap.to_xml(true)
51
+
52
+ # The third time we generate the document, we should have a signature
53
+ http.body = soap.to_xml(true)
54
+ else
55
+ http.body = soap.to_xml
56
+ end
57
+
45
58
  http.headers["Content-Type"] = ContentType[soap.version]
46
59
  http.headers["Content-Length"] = soap.to_xml.bytesize.to_s
47
60
  http
@@ -1,5 +1,6 @@
1
1
  require "builder"
2
2
  require "gyoku"
3
+ require "rexml/document"
3
4
  require "nori"
4
5
 
5
6
  require "savon/soap"
@@ -24,12 +25,9 @@ module Savon
24
25
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
25
26
  }
26
27
 
27
- # Accepts an +endpoint+, an +input+ tag and a SOAP +body+.
28
- def initialize(config, endpoint = nil, input = nil, body = nil)
28
+ # Expects a +config+ object.
29
+ def initialize(config)
29
30
  self.config = config
30
- self.endpoint = endpoint if endpoint
31
- self.input = input if input
32
- self.body = body if body
33
31
  end
34
32
 
35
33
  attr_accessor :config
@@ -123,6 +121,10 @@ module Savon
123
121
  # Accessor for the <tt>Savon::WSSE</tt> object.
124
122
  attr_accessor :wsse
125
123
 
124
+ def signature?
125
+ wsse.respond_to?(:signature?) && wsse.signature?
126
+ end
127
+
126
128
  # Returns the SOAP request encoding. Defaults to "UTF-8".
127
129
  def encoding
128
130
  @encoding ||= "UTF-8"
@@ -152,14 +154,23 @@ module Savon
152
154
  attr_writer :xml
153
155
 
154
156
  # Returns the XML for a SOAP request.
155
- def to_xml
157
+ def to_xml(clear_cache = false)
158
+ if clear_cache
159
+ @xml = nil
160
+ @header_for_xml = nil
161
+ end
162
+
156
163
  @xml ||= tag(builder, :Envelope, complete_namespaces) do |xml|
157
164
  tag(xml, :Header) { xml << header_for_xml } unless header_for_xml.empty?
158
165
 
166
+ # TODO: Maybe there should be some sort of plugin architecture where
167
+ # classes like WSSE::Signature can hook into this process.
168
+ body_attributes = (signature? ? wsse.signature.body_attributes : {})
169
+
159
170
  if input.nil?
160
- tag(xml, :Body)
171
+ tag(xml, :Body, body_attributes)
161
172
  else
162
- tag(xml, :Body) { xml.tag!(*add_namespace_to_input) { xml << body_to_xml } }
173
+ tag(xml, :Body, body_attributes) { xml.tag!(*add_namespace_to_input) { xml << body_to_xml } }
163
174
  end
164
175
  end
165
176
  end
@@ -203,7 +214,8 @@ module Savon
203
214
  # Returns the SOAP body as an XML String.
204
215
  def body_to_xml
205
216
  return body.to_s unless body.kind_of? Hash
206
- Gyoku.xml add_namespaces_to_body(body), :element_form_default => element_form_default, :namespace => namespace_identifier
217
+ body_to_xml = element_form_default == :qualified ? add_namespaces_to_body(body) : body
218
+ Gyoku.xml body_to_xml, :element_form_default => element_form_default, :namespace => namespace_identifier
207
219
  end
208
220
 
209
221
  def add_namespaces_to_body(hash, path = [input[1].to_s])
@@ -221,6 +233,7 @@ module Savon
221
233
  add_namespaces_to_body(value, types[newpath] ? [types[newpath]] : newpath)
222
234
  )
223
235
  else
236
+ add_namespaces_to_values(value, path) if key == :order!
224
237
  newhash.merge(key => value)
225
238
  end
226
239
  end
@@ -231,6 +244,14 @@ module Savon
231
244
  [used_namespaces[[input[1].to_s]], input[1], input[2]]
232
245
  end
233
246
 
247
+ def add_namespaces_to_values(values, path)
248
+ values.collect! { |value|
249
+ camelcased_value = Gyoku::XMLKey.create(value)
250
+ namespace_path = path + [camelcased_value.to_s]
251
+ namespace = used_namespaces[namespace_path]
252
+ "#{namespace.blank? ? '' : namespace + ":"}#{camelcased_value}"
253
+ }
254
+ end
234
255
  end
235
256
  end
236
257
  end
data/lib/savon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Savon
2
2
 
3
- Version = "0.9.10"
3
+ Version = "0.9.11"
4
4
 
5
5
  end
data/savon.gemspec CHANGED
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.add_dependency "builder", ">= 2.1.2"
19
19
  s.add_dependency "nori", "~> 1.1"
20
20
  s.add_dependency "httpi", "~> 0.9"
21
- s.add_dependency "wasabi", "~> 2.1"
22
- s.add_dependency "akami", "~> 1.0"
21
+ s.add_dependency "wasabi", "~> 2.2"
22
+ s.add_dependency "akami", "~> 1.1"
23
23
  s.add_dependency "gyoku", ">= 0.4.0"
24
24
  s.add_dependency "nokogiri", ">= 1.4.0"
25
25
 
@@ -100,6 +100,24 @@ describe Savon::Client do
100
100
 
101
101
  client.request(:get_user) { soap.namespace = nil }
102
102
  end
103
+
104
+ context "when the wsdl's operation namespace identifier matches a document identifier" do
105
+ before do
106
+ client.wsdl.operations[:authenticate][:namespace_identifier] = "tns"
107
+ end
108
+
109
+ it "sets the soap's namespace identifier to the matching operation's namespace identifier" do
110
+ client.request(:authenticate) { soap.namespace_identifier.should == :tns }
111
+ end
112
+
113
+ it "sets the soap's namespace to the namspace matching the identifier" do
114
+ client.request(:authenticate) { soap.namespace.should == "http://v1_0.ws.auth.order.example.com/" }
115
+ end
116
+
117
+ it "sets the input tag to result in <tns:authenticate>" do
118
+ client.request(:authenticate) { soap.input.should == [:tns, :authenticate, {}] }
119
+ end
120
+ end
103
121
  end
104
122
 
105
123
  context "with a single argument (String)" do
@@ -206,7 +224,7 @@ describe Savon::Client do
206
224
  client.http.headers.expects(:[]=).with("Cookie", anything).never
207
225
  client.http.headers.stubs(:[]=).with("SOAPAction", '"authenticate"')
208
226
  client.http.headers.stubs(:[]=).with("Content-Type", "text/xml;charset=UTF-8")
209
- client.http.headers.stubs(:[]=).with("Content-Length", "384")
227
+ client.http.headers.stubs(:[]=).with("Content-Length", "383")
210
228
 
211
229
  client.request :authenticate
212
230
  end
@@ -215,17 +233,22 @@ describe Savon::Client do
215
233
  context "#request with a Set-Cookie response header" do
216
234
  before do
217
235
  HTTPI.stubs(:get).returns(new_response(:body => Fixture.wsdl(:authentication)))
218
- HTTPI.stubs(:post).returns(new_response(:headers => { "Set-Cookie" => "some-cookie" }))
236
+ HTTPI.stubs(:post).returns(new_response(:headers => { "Set-Cookie" => "some-cookie=choc-chip; Path=/; HttpOnly" }))
219
237
  end
220
238
 
221
239
  it "should set the Cookie header for the next request" do
222
- client.http.headers.expects(:[]=).with("Cookie", "some-cookie")
223
- client.http.headers.stubs(:[]=).with("SOAPAction", '"authenticate"')
224
- client.http.headers.stubs(:[]=).with("Content-Type", "text/xml;charset=UTF-8")
225
- client.http.headers.stubs(:[]=).with("Content-Length", "384")
240
+ client.request :authenticate
241
+ client.http.headers["Cookie"].should == "some-cookie=choc-chip"
242
+ end
226
243
 
244
+ it "should set additional cookies when new cookies are found in subsequent requests" do
227
245
  client.request :authenticate
246
+ HTTPI.stubs(:post).returns(new_response(:headers => { "Set-Cookie" => "second-cookie=oatmeal; Path=/; HttpOnly" }))
247
+ client.request :nibble
248
+
249
+ client.http.headers["Cookie"].should == "some-cookie=choc-chip;second-cookie=oatmeal"
228
250
  end
251
+
229
252
  end
230
253
 
231
254
  context "with a remote WSDL document" do
@@ -382,7 +405,7 @@ describe Savon::Client do
382
405
 
383
406
  it "should namespaces each Array item as expected" do
384
407
  HTTPI::Request.any_instance.expects(:body=).with do |value|
385
- value.include?("<ins0:cartItems><ins0:CartItem>") && value.include?("<wsdl:ItemID>SKU-TEST</wsdl:ItemID>")
408
+ value.include?("<ins0:cartItems><ins0:CartItem>") && value.include?("<tns:ItemID>SKU-TEST</tns:ItemID>")
386
409
  end
387
410
 
388
411
  address = { "Address1" => "888 6th Ave", "Address2" => nil, "City" => "New York", "State" => "NY", "Zip5" => "10001", "Zip4" => nil }
@@ -1,11 +1,23 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Savon::SOAP::Request do
4
- let(:soap_request) { Savon::SOAP::Request.new(config, http_request, soap) }
4
+
5
+ let(:soap_request) { Savon::SOAP::Request.new(config, http_request, soap_xml) }
5
6
  let(:http_request) { HTTPI::Request.new }
6
- let(:soap) { Savon::SOAP::XML.new config, Endpoint.soap, [nil, :get_user, {}], :id => 1 }
7
7
  let(:config) { Savon::Config.default }
8
8
 
9
+ def soap_xml(*args)
10
+ @soap_xml ||= soap_xml!(*args)
11
+ end
12
+
13
+ def soap_xml!(endpoint = nil, input = nil, body = nil)
14
+ soap = Savon::SOAP::XML.new(config)
15
+ soap.endpoint = endpoint || Endpoint.soap
16
+ soap.input = input || [nil, :get_user, {}]
17
+ soap.body = body || { :id => 1 }
18
+ soap
19
+ end
20
+
9
21
  it "contains the content type for each supported SOAP version" do
10
22
  content_type = Savon::SOAP::Request::ContentType
11
23
  content_type[1].should == "text/xml;charset=UTF-8"
@@ -15,18 +27,18 @@ describe Savon::SOAP::Request do
15
27
  describe ".execute" do
16
28
  it "executes a SOAP request and returns the response" do
17
29
  HTTPI.expects(:post).returns(HTTPI::Response.new 200, {}, Fixture.response(:authentication))
18
- response = Savon::SOAP::Request.execute config, http_request, soap
30
+ response = Savon::SOAP::Request.execute config, http_request, soap_xml
19
31
  response.should be_a(Savon::SOAP::Response)
20
32
  end
21
33
  end
22
34
 
23
35
  describe ".new" do
24
36
  it "uses the SOAP endpoint for the request" do
25
- soap_request.http.url.should == URI(soap.endpoint)
37
+ soap_request.http.url.should == URI(soap_xml.endpoint)
26
38
  end
27
39
 
28
40
  it "sets the SOAP body for the request" do
29
- soap_request.http.body.should == soap.to_xml
41
+ soap_request.http.body.should == soap_xml.to_xml
30
42
  end
31
43
 
32
44
  it "sets the Content-Type header for SOAP 1.1" do
@@ -34,21 +46,21 @@ describe Savon::SOAP::Request do
34
46
  end
35
47
 
36
48
  it "sets the Content-Type header for SOAP 1.2" do
37
- soap.version = 2
49
+ soap_xml.version = 2
38
50
  soap_request.http.headers["Content-Type"].should == Savon::SOAP::Request::ContentType[2]
39
51
  end
40
52
 
41
53
  it "sets the Content-Length header" do
42
- soap_request.http.headers["Content-Length"].should == soap.to_xml.length.to_s
54
+ soap_request.http.headers["Content-Length"].should == soap_xml.to_xml.length.to_s
43
55
  end
44
56
 
45
57
  it "sets the Content-Length header for every request" do
46
58
  http = HTTPI::Request.new
47
- soap_request = Savon::SOAP::Request.new(config, http, soap)
59
+ soap_request = Savon::SOAP::Request.new(config, http, soap_xml)
48
60
  http.headers.should include("Content-Length" => "272")
49
61
 
50
- soap = Savon::SOAP::XML.new config, Endpoint.soap, [nil, :create_user, {}], :id => 123
51
- soap_request = Savon::SOAP::Request.new(config, http, soap)
62
+ soap_xml = soap_xml!(Endpoint.soap, [nil, :create_user, {}], :id => 123)
63
+ soap_request = Savon::SOAP::Request.new(config, http, soap_xml)
52
64
  http.headers.should include("Content-Length" => "280")
53
65
  end
54
66
  end
@@ -1,19 +1,19 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Savon::SOAP::XML do
4
- let(:xml) { Savon::SOAP::XML.new(config, Endpoint.soap, [nil, :authenticate, {}], :id => 1) }
5
- let(:config) { Savon::Config.default }
6
-
7
- describe ".new" do
8
- it "should accept an endpoint, an input tag and a SOAP body" do
9
- xml = Savon::SOAP::XML.new(config, Endpoint.soap, [nil, :authentication, {}], :id => 1)
10
4
 
11
- xml.endpoint.should == Endpoint.soap
12
- xml.input.should == [nil, :authentication, {}]
13
- xml.body.should == { :id => 1 }
5
+ def xml(endpoint = nil, input = nil, body = nil)
6
+ @xml ||= begin
7
+ xml = Savon::SOAP::XML.new(config)
8
+ xml.endpoint = endpoint || Endpoint.soap
9
+ xml.input = input || [nil, :authenticate, {}]
10
+ xml.body = body || { :id => 1 }
11
+ xml
14
12
  end
15
13
  end
16
14
 
15
+ let(:config) { Savon::Config.default }
16
+
17
17
  describe "#input" do
18
18
  it "should set the input tag" do
19
19
  xml.input = :test
@@ -260,19 +260,22 @@ describe Savon::SOAP::XML do
260
260
  end
261
261
 
262
262
  context "with :element_form_default set to :qualified and a :namespace" do
263
- let :xml do
264
- Savon::SOAP::XML.new(config, Endpoint.soap, [nil, :authenticate, {}], :user => { :id => 1, ":noNamespace" => true })
263
+ it "should namespace the default elements" do
264
+ xml = xml(Endpoint.soap, [nil, :authenticate, {}], :user => { :id => 1, ":noNamespace" => true })
265
+ xml.element_form_default = :qualified
266
+ xml.namespace_identifier = :wsdl
267
+
268
+ xml.to_xml.should include("<wsdl:user>", "<wsdl:id>1</wsdl:id>", "<noNamespace>true</noNamespace>")
265
269
  end
270
+ end
266
271
 
272
+ context "with :element_form_default set to :unqualified and a :namespace" do
267
273
  it "should namespace the default elements" do
268
- xml.element_form_default = :qualified
274
+ xml = xml(Endpoint.soap, [nil, :authenticate, {}], :user => { :id => 1, ":noNamespace" => true })
275
+ xml.element_form_default = :unqualified
269
276
  xml.namespace_identifier = :wsdl
270
277
 
271
- xml.to_xml.should include(
272
- "<wsdl:user>",
273
- "<wsdl:id>1</wsdl:id>",
274
- "<noNamespace>true</noNamespace>"
275
- )
278
+ xml.to_xml.should include("<user>", "<id>1</id>", "<noNamespace>true</noNamespace>")
276
279
  end
277
280
  end
278
281
 
@@ -323,5 +326,32 @@ describe Savon::SOAP::XML do
323
326
  end
324
327
  end
325
328
 
329
+ describe "#add_namespaces_to_body" do
330
+ before :each do
331
+ xml.used_namespaces.merge!({
332
+ ["authenticate", "id"] =>"ns0",
333
+ ["authenticate", "name"] =>"ns1",
334
+ ["authenticate", "name", "firstName"] =>"ns2"
335
+ })
336
+ end
337
+
338
+ it "adds namespaces" do
339
+ body = {:id => 1, :name => {:first_name => 'Bob'}}
340
+ xml.send(:add_namespaces_to_body, body).should == {"ns0:id" => "1", "ns1:name" => {"ns2:firstName" => "Bob"}}
341
+ end
342
+
343
+ it "adds namespaces to order! list" do
344
+ body = {:id => 1, :name => {:first_name => 'Bob', :order! => [:first_name]}, :order! => [:id, :name]}
345
+ xml.send(:add_namespaces_to_body, body).should == {
346
+ "ns0:id" => "1",
347
+ "ns1:name" => {
348
+ "ns2:firstName" => "Bob",
349
+ :order! => ["ns2:firstName"]
350
+ },
351
+ :order! => ["ns0:id", "ns1:name"]
352
+ }
353
+ end
354
+ end
355
+
326
356
  end
327
357
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: savon
3
3
  version: !ruby/object:Gem::Version
4
- hash: 47
4
+ hash: 45
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 10
10
- version: 0.9.10
9
+ - 11
10
+ version: 0.9.11
11
11
  platform: ruby
12
12
  authors:
13
13
  - Daniel Harrington
@@ -69,11 +69,11 @@ dependencies:
69
69
  requirements:
70
70
  - - ~>
71
71
  - !ruby/object:Gem::Version
72
- hash: 1
72
+ hash: 7
73
73
  segments:
74
74
  - 2
75
- - 1
76
- version: "2.1"
75
+ - 2
76
+ version: "2.2"
77
77
  name: wasabi
78
78
  type: :runtime
79
79
  prerelease: false
@@ -84,11 +84,11 @@ dependencies:
84
84
  requirements:
85
85
  - - ~>
86
86
  - !ruby/object:Gem::Version
87
- hash: 15
87
+ hash: 13
88
88
  segments:
89
89
  - 1
90
- - 0
91
- version: "1.0"
90
+ - 1
91
+ version: "1.1"
92
92
  name: akami
93
93
  type: :runtime
94
94
  prerelease: false