savon 0.8.1 → 0.8.2

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