savon 0.8.1 → 0.8.2

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,8 +1,34 @@
1
- ## 0.8.1
1
+ ## 0.8.2 (2010-01-04)
2
+
3
+ * Fix for [issue #127](https://github.com/rubiii/savon/issues/127) ([0eb3da](https://github.com/rubiii/savon/commit/0eb3da4))
4
+
5
+ * Changed `Savon::WSSE` to be based on a Hash instead of relying on builder ([4cebc3](https://github.com/rubiii/savon/commit/4cebc3)).
6
+
7
+ `Savon::WSSE` now supports wsse:Timestamp headers ([issue #122](https://github.com/rubiii/savon/issues/122)) by setting
8
+ `Savon::WSSE#timestamp` to `true`:
9
+
10
+ client.request :some_method do
11
+ wsse.timestamp = true
12
+ end
13
+
14
+ or by setting `Savon::WSSE#created_at` or `Savon::WSSE#expires_at`:
15
+
16
+ client.request :some_method do
17
+ wsse.created_at = Time.now
18
+ wsse.expires_at = Time.now + 60
19
+ end
20
+
21
+ You can also add custom tags to the WSSE header ([issue #69](https://github.com/rubiii/savon/issues/69)):
22
+
23
+ client.request :some_method do
24
+ wsse["wsse:Security"]["wsse:UsernameToken"] = { "Organization" => "ACME", "Domain" => "acme.com" }
25
+ end
26
+
27
+ ## 0.8.1 (2010-12-22)
2
28
 
3
29
  * Update to depend on HTTPI v0.7.5 which comes with a fallback to use Net::HTTP when no other adapter could be required.
4
30
 
5
- * Fix for [issue #72](https://github.com/rubiii/savon/issues/72) ([22074a8](https://github.com/rubiii/savon/commit/22074a8)).
31
+ * Fix for [issue #72](https://github.com/rubiii/savon/issues/72) ([22074a](https://github.com/rubiii/savon/commit/22074a8)).
6
32
 
7
33
  * Loosen dependency on builder. Should be quite stable.
8
34
 
@@ -8,6 +8,16 @@ module Savon
8
8
  module CoreExt
9
9
  module Hash
10
10
 
11
+ # Returns a new Hash with +self+ and +other_hash+ merged recursively.
12
+ # Modifies the receiver in place.
13
+ def deep_merge!(other_hash)
14
+ other_hash.each_pair do |k,v|
15
+ tv = self[k]
16
+ self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
17
+ end
18
+ self
19
+ end unless defined? deep_merge!
20
+
11
21
  # Returns the values from the soap:Body element or an empty Hash in case the soap:Body tag could
12
22
  # not be found.
13
23
  def find_soap_body
@@ -0,0 +1,14 @@
1
+ module Savon
2
+ module CoreExt
3
+ module Time
4
+
5
+ # Returns an xs:dateTime formatted String.
6
+ def xs_datetime
7
+ strftime "%Y-%m-%dT%H:%M:%S%Z"
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+
14
+ Time.send :include, Savon::CoreExt::Time
data/lib/savon/soap.rb CHANGED
@@ -17,9 +17,6 @@ module Savon
17
17
  2 => "http://www.w3.org/2003/05/soap-envelope"
18
18
  }
19
19
 
20
- # SOAP xs:dateTime format.
21
- DateTimeFormat = "%Y-%m-%dT%H:%M:%S%Z"
22
-
23
20
  # SOAP xs:dateTime Regexp.
24
21
  DateTimeRegexp = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
25
22
 
@@ -19,7 +19,7 @@ module Savon
19
19
 
20
20
  # Returns whether a SOAP fault is present.
21
21
  def present?
22
- @present ||= http.body =~ /<(.+:)?Body>(\s*)<(.+:)?Fault>/
22
+ @present ||= http.body[0,1000] =~ /<(.+:)?Body>(\s*)<(.+:)?Fault>/
23
23
  end
24
24
 
25
25
  # Returns the SOAP fault message.
data/lib/savon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Savon
2
2
 
3
- Version = "0.8.1"
3
+ Version = "0.8.2"
4
4
 
5
5
  end
data/lib/savon/wsse.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require "base64"
2
2
  require "digest/sha1"
3
- require "builder"
4
3
 
5
4
  require "savon/core_ext/string"
6
- require "savon/soap"
5
+ require "savon/core_ext/hash"
6
+ require "savon/core_ext/time"
7
7
 
8
8
  module Savon
9
9
 
@@ -24,14 +24,25 @@ module Savon
24
24
  # URI for "wsse:Password/@Type" #PasswordDigest.
25
25
  PasswordDigestURI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
26
26
 
27
- # Sets the authentication credentials. Also accepts whether to use WSSE digest.
27
+ # Returns a value from the WSSE Hash.
28
+ def [](key)
29
+ hash[key]
30
+ end
31
+
32
+ # Sets a value on the WSSE Hash.
33
+ def []=(key, value)
34
+ hash[key] = value
35
+ end
36
+
37
+ # Sets authentication credentials for a wsse:UsernameToken header.
38
+ # Also accepts whether to use WSSE digest authentication.
28
39
  def credentials(username, password, digest = false)
29
40
  self.username = username
30
41
  self.password = password
31
42
  self.digest = digest
32
43
  end
33
44
 
34
- attr_accessor :username, :password
45
+ attr_accessor :username, :password, :created_at, :expires_at
35
46
 
36
47
  # Returns whether to use WSSE digest. Defaults to +false+.
37
48
  def digest?
@@ -40,26 +51,64 @@ module Savon
40
51
 
41
52
  attr_writer :digest
42
53
 
43
- # Returns the XML for a WSSE header or an empty String unless authentication
44
- # credentials were specified.
54
+ # Returns whether to generate a wsse:UsernameToken header.
55
+ def username_token?
56
+ username && password
57
+ end
58
+
59
+ # Returns whether to generate a wsse:Timestamp header.
60
+ def timestamp?
61
+ created_at || expires_at || @wsse_timestamp
62
+ end
63
+
64
+ # Sets whether to generate a wsse:Timestamp header.
65
+ def timestamp=(timestamp)
66
+ @wsse_timestamp = timestamp
67
+ end
68
+
69
+ # Returns the XML for a WSSE header.
45
70
  def to_xml
46
- return "" unless username && password
47
-
48
- builder = Builder::XmlMarkup.new
49
- builder.wsse :Security, "xmlns:wsse" => WSENamespace do |xml|
50
- xml.wsse :UsernameToken, "wsu:Id" => wsu_id, "xmlns:wsu" => WSUNamespace do
51
- xml.wsse :Username, username
52
- xml.wsse :Nonce, nonce
53
- xml.wsu :Created, timestamp
54
- xml.wsse :Password, password_node, :Type => password_type
55
- end
71
+ if username_token?
72
+ Gyoku.xml wsse_username_token.merge!(hash)
73
+ elsif timestamp?
74
+ Gyoku.xml wsse_timestamp.merge!(hash)
75
+ else
76
+ ""
56
77
  end
57
78
  end
58
79
 
59
80
  private
60
81
 
82
+ # Returns a Hash containing wsse:UsernameToken details.
83
+ def wsse_username_token
84
+ wsse_security "UsernameToken",
85
+ "wsse:Username" => username,
86
+ "wsse:Nonce" => nonce,
87
+ "wsu:Created" => timestamp,
88
+ "wsse:Password" => password_value,
89
+ :attributes! => { "wsse:Password" => { "Type" => password_type } }
90
+ end
91
+
92
+ # Returns a Hash containing wsse:Timestamp details.
93
+ def wsse_timestamp
94
+ wsse_security "Timestamp",
95
+ "wsu:Created" => (created_at || Time.now).xs_datetime,
96
+ "wsu:Expires" => (expires_at || (created_at || Time.now) + 60).xs_datetime
97
+ end
98
+
99
+ # Returns a Hash containing wsse:Security details for a given +tag+ and +hash+.
100
+ def wsse_security(tag, hash)
101
+ {
102
+ "wsse:Security" => {
103
+ "wsse:#{tag}" => hash,
104
+ :attributes! => { "wsse:#{tag}" => { "wsu:Id" => "#{tag}-#{count}", "xmlns:wsu" => WSUNamespace } }
105
+ },
106
+ :attributes! => { "wsse:Security" => { "xmlns:wsse" => WSENamespace } }
107
+ }
108
+ end
109
+
61
110
  # Returns the WSSE password. Encrypts the password for digest authentication.
62
- def password_node
111
+ def password_value
63
112
  return password unless digest?
64
113
 
65
114
  token = nonce + timestamp + password
@@ -83,19 +132,19 @@ module Savon
83
132
 
84
133
  # Returns a WSSE timestamp.
85
134
  def timestamp
86
- @timestamp ||= Time.now.strftime Savon::SOAP::DateTimeFormat
87
- end
88
-
89
- # Returns the "wsu:Id" attribute.
90
- def wsu_id
91
- "UsernameToken-#{count}"
135
+ @timestamp ||= Time.now.xs_datetime
92
136
  end
93
137
 
94
- # Simple counter.
138
+ # Returns a new number with every call.
95
139
  def count
96
140
  @count ||= 0
97
141
  @count += 1
98
142
  end
99
143
 
144
+ # Returns a memoized and autovivificating Hash.
145
+ def hash
146
+ @hash ||= Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
147
+ end
148
+
100
149
  end
101
150
  end
data/savon.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency "rspec", "~> 2.0.0"
23
23
  s.add_development_dependency "autotest"
24
24
  s.add_development_dependency "mocha", "~> 0.9.7"
25
+ s.add_development_dependency "timecop", "~> 0.3.5"
25
26
 
26
27
  s.files = `git ls-files`.split("\n")
27
28
  s.require_path = "lib"
@@ -2,6 +2,15 @@ require "spec_helper"
2
2
 
3
3
  describe Hash do
4
4
 
5
+ describe "#deep_merge!" do
6
+ it "should recursively merge two Hashes" do
7
+ hash = { :one => 1, "two" => { "three" => 3 } }
8
+ other_hash = { :four => 4, "two" => { "three" => "merge", :five => 5 } }
9
+
10
+ hash.merge!(other_hash).should == { :one => 1, :four => 4, "two" => { "three" => "merge", :five => 5 } }
11
+ end
12
+ end
13
+
5
14
  describe "find_soap_body" do
6
15
  it "should return the content from the 'soap:Body' element" do
7
16
  soap_body = { "soap:Envelope" => { "soap:Body" => "content" } }
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe Time do
4
+
5
+ describe "#xs_datetime" do
6
+ let(:time) { Time.utc(2011, 01, 04, 13, 45, 55) }
7
+
8
+ it "should return an xs:dateTime formatted String" do
9
+ time.xs_datetime.should == "2011-01-04T13:45:55UTC"
10
+ end
11
+ end
12
+
13
+ end
@@ -13,17 +13,9 @@ describe Savon::SOAP do
13
13
  Savon::SOAP::Versions.should == (1..2)
14
14
  end
15
15
 
16
- it "should contain the xs:dateTime format" do
17
- Savon::SOAP::DateTimeFormat.should be_a(String)
18
- Savon::SOAP::DateTimeFormat.should_not be_empty
19
-
20
- DateTime.new(2012, 03, 22, 16, 22, 33).strftime(Savon::SOAP::DateTimeFormat).
21
- should == "2012-03-22T16:22:33+00:00"
22
- end
23
-
24
16
  it "should contain a Regexp matching the xs:dateTime format" do
25
17
  Savon::SOAP::DateTimeRegexp.should be_a(Regexp)
26
18
  (Savon::SOAP::DateTimeRegexp === "2012-03-22T16:22:33").should be_true
27
19
  end
28
-
20
+
29
21
  end
@@ -158,6 +158,56 @@ describe Savon::WSSE do
158
158
  wsse.to_xml.should include(Savon::WSSE::PasswordDigestURI)
159
159
  end
160
160
  end
161
+
162
+ context "with #timestamp set to true" do
163
+ before { wsse.timestamp = true }
164
+
165
+ it "should contain a wsse:Timestamp node" do
166
+ wsse.to_xml.should include('<wsse:Timestamp wsu:Id="Timestamp-1" ' +
167
+ 'xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">')
168
+ end
169
+
170
+ it "should contain a wsu:Created node defaulting to Time.now" do
171
+ created_at = Time.now
172
+ Timecop.freeze created_at do
173
+ wsse.to_xml.should include("<wsu:Created>#{created_at.xs_datetime}</wsu:Created>")
174
+ end
175
+ end
176
+
177
+ it "should contain a wsu:Expires node defaulting to Time.now + 60 seconds" do
178
+ created_at = Time.now
179
+ Timecop.freeze created_at do
180
+ wsse.to_xml.should include("<wsu:Expires>#{(created_at + 60).xs_datetime}</wsu:Expires>")
181
+ end
182
+ end
183
+ end
184
+
185
+ context "with #created_at" do
186
+ before { wsse.created_at = Time.now + 86400 }
187
+
188
+ it "should contain a wsu:Created node with the given time" do
189
+ wsse.to_xml.should include("<wsu:Created>#{wsse.created_at.xs_datetime}</wsu:Created>")
190
+ end
191
+
192
+ it "should contain a wsu:Expires node set to #created_at + 60 seconds" do
193
+ wsse.to_xml.should include("<wsu:Expires>#{(wsse.created_at + 60).xs_datetime}</wsu:Expires>")
194
+ end
195
+ end
196
+
197
+ context "with #expires_at" do
198
+ before { wsse.expires_at = Time.now + 86400 }
199
+
200
+ it "should contain a wsu:Created node defaulting to Time.now" do
201
+ created_at = Time.now
202
+ Timecop.freeze created_at do
203
+ wsse.to_xml.should include("<wsu:Created>#{created_at.xs_datetime}</wsu:Created>")
204
+ end
205
+ end
206
+
207
+ it "should contain a wsu:Expires node set to the given time" do
208
+ wsse.to_xml.should include("<wsu:Expires>#{wsse.expires_at.xs_datetime}</wsu:Expires>")
209
+ end
210
+ end
161
211
  end
162
212
 
163
213
  end
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: 61
5
- prerelease: false
4
+ hash: 59
5
+ prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 8
9
- - 1
10
- version: 0.8.1
9
+ - 2
10
+ version: 0.8.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Daniel Harrington
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-22 00:00:00 +01:00
18
+ date: 2011-01-04 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -128,6 +128,22 @@ dependencies:
128
128
  version: 0.9.7
129
129
  type: :development
130
130
  version_requirements: *id007
131
+ - !ruby/object:Gem::Dependency
132
+ name: timecop
133
+ prerelease: false
134
+ requirement: &id008 !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ~>
138
+ - !ruby/object:Gem::Version
139
+ hash: 25
140
+ segments:
141
+ - 0
142
+ - 3
143
+ - 5
144
+ version: 0.3.5
145
+ type: :development
146
+ version_requirements: *id008
131
147
  description: Savon is the heavy metal Ruby SOAP client.
132
148
  email: me@rubiii.com
133
149
  executables: []
@@ -151,6 +167,7 @@ files:
151
167
  - lib/savon/core_ext/hash.rb
152
168
  - lib/savon/core_ext/object.rb
153
169
  - lib/savon/core_ext/string.rb
170
+ - lib/savon/core_ext/time.rb
154
171
  - lib/savon/error.rb
155
172
  - lib/savon/global.rb
156
173
  - lib/savon/http/error.rb
@@ -180,6 +197,7 @@ files:
180
197
  - spec/savon/core_ext/hash_spec.rb
181
198
  - spec/savon/core_ext/object_spec.rb
182
199
  - spec/savon/core_ext/string_spec.rb
200
+ - spec/savon/core_ext/time_spec.rb
183
201
  - spec/savon/http/error_spec.rb
184
202
  - spec/savon/savon_spec.rb
185
203
  - spec/savon/soap/fault_spec.rb
@@ -224,7 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
242
  requirements: []
225
243
 
226
244
  rubyforge_project: savon
227
- rubygems_version: 1.3.7
245
+ rubygems_version: 1.4.1
228
246
  signing_key:
229
247
  specification_version: 3
230
248
  summary: Heavy metal Ruby SOAP client