savon 0.9.10 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
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