tictoc-savon 0.7.9

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.
Files changed (59) hide show
  1. data/.autotest +5 -0
  2. data/CHANGELOG +176 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +64 -0
  5. data/Rakefile +50 -0
  6. data/lib/savon.rb +35 -0
  7. data/lib/savon/client.rb +131 -0
  8. data/lib/savon/core_ext.rb +8 -0
  9. data/lib/savon/core_ext/array.rb +31 -0
  10. data/lib/savon/core_ext/datetime.rb +10 -0
  11. data/lib/savon/core_ext/hash.rb +107 -0
  12. data/lib/savon/core_ext/net_http.rb +19 -0
  13. data/lib/savon/core_ext/object.rb +16 -0
  14. data/lib/savon/core_ext/string.rb +69 -0
  15. data/lib/savon/core_ext/symbol.rb +8 -0
  16. data/lib/savon/core_ext/uri.rb +10 -0
  17. data/lib/savon/logger.rb +56 -0
  18. data/lib/savon/request.rb +138 -0
  19. data/lib/savon/response.rb +174 -0
  20. data/lib/savon/soap.rb +302 -0
  21. data/lib/savon/version.rb +5 -0
  22. data/lib/savon/wsdl.rb +137 -0
  23. data/lib/savon/wsdl_stream.rb +85 -0
  24. data/lib/savon/wsse.rb +163 -0
  25. data/spec/basic_spec_helper.rb +11 -0
  26. data/spec/endpoint_helper.rb +23 -0
  27. data/spec/fixtures/gzip/gzip_response_fixture.rb +7 -0
  28. data/spec/fixtures/gzip/message.gz +0 -0
  29. data/spec/fixtures/response/response_fixture.rb +36 -0
  30. data/spec/fixtures/response/xml/authentication.xml +14 -0
  31. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  32. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  33. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  34. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  35. data/spec/fixtures/wsdl/wsdl_fixture.yml +42 -0
  36. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  37. data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
  38. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  39. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  40. data/spec/http_stubs.rb +26 -0
  41. data/spec/integration/http_basic_auth_spec.rb +16 -0
  42. data/spec/integration/server.rb +51 -0
  43. data/spec/savon/client_spec.rb +86 -0
  44. data/spec/savon/core_ext/array_spec.rb +49 -0
  45. data/spec/savon/core_ext/datetime_spec.rb +21 -0
  46. data/spec/savon/core_ext/hash_spec.rb +190 -0
  47. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  48. data/spec/savon/core_ext/object_spec.rb +34 -0
  49. data/spec/savon/core_ext/string_spec.rb +99 -0
  50. data/spec/savon/core_ext/symbol_spec.rb +12 -0
  51. data/spec/savon/core_ext/uri_spec.rb +19 -0
  52. data/spec/savon/request_spec.rb +117 -0
  53. data/spec/savon/response_spec.rb +179 -0
  54. data/spec/savon/soap_spec.rb +202 -0
  55. data/spec/savon/wsdl_spec.rb +107 -0
  56. data/spec/savon/wsse_spec.rb +132 -0
  57. data/spec/spec.opts +4 -0
  58. data/spec/spec_helper.rb +5 -0
  59. metadata +229 -0
@@ -0,0 +1,5 @@
1
+ module Savon
2
+
3
+ Version = "0.7.9"
4
+
5
+ end
data/lib/savon/wsdl.rb ADDED
@@ -0,0 +1,137 @@
1
+ module Savon
2
+
3
+ # = Savon::WSDL
4
+ #
5
+ # Savon::WSDL represents the WSDL of your service, including information like the namespace URI,
6
+ # the SOAP endpoint and available SOAP actions.
7
+ #
8
+ # == The WSDL document
9
+ #
10
+ # Retrieve the raw WSDL document:
11
+ #
12
+ # client.wsdl.to_s
13
+ #
14
+ # == Available SOAP actions
15
+ #
16
+ # Get an array of available SOAP actions:
17
+ #
18
+ # client.wsdl.soap_actions
19
+ # # => [:get_all_users, :get_user_by_id]
20
+ #
21
+ # == Namespace URI
22
+ #
23
+ # Get the namespace URI:
24
+ #
25
+ # client.wsdl.namespace_uri
26
+ # # => "http://ws.userservice.example.com"
27
+ #
28
+ # == SOAP endpoint
29
+ #
30
+ # Get the SOAP endpoint:
31
+ #
32
+ # client.wsdl.soap_endpoint
33
+ # # => "http://example.com"
34
+ #
35
+ # == Disable Savon::WSDL
36
+ #
37
+ # Especially with large services (i.e. Ebay), getting and parsing the WSDL document can really
38
+ # slow down your request. The WSDL is great for exploring a service, but it's recommended to
39
+ # disable it for production.
40
+ #
41
+ # When disabling the WSDL, you need to pay attention to certain differences:
42
+ #
43
+ # 1. You instantiate Savon::Client with the actual SOAP endpoint instead of pointing it to the
44
+ # WSDL of your service.
45
+ # 2. You also need to manually specify the SOAP.namespace.
46
+ # 3. Append an exclamation mark (!) to your SOAP call:
47
+ #
48
+ # client = Savon::Client.new "http://example.com"
49
+ #
50
+ # client.get_user_by_id! do |soap|
51
+ # soap.namespace = "http://example.com/UserService"
52
+ # soap.body = { :id => 666 }
53
+ # end
54
+ #
55
+ # Without the WSDL, Savon also has to guess the name of the SOAP action and input tag. It takes
56
+ # the name of the method called on its client instance, converts it from snake_case to lowerCamelCase
57
+ # and uses the result.
58
+ #
59
+ # The example above expects a SOAP action with an original name of "getUserById". If you service
60
+ # uses UpperCamelCase method names, you can just use the original name:
61
+ #
62
+ # client.GetAllUsers!
63
+ #
64
+ # For special cases, you could also specify the SOAP.action and SOAP.input inside the block:
65
+ #
66
+ # client.get_user_by_id! do |soap|
67
+ # soap.namespace = "http://example.com/UserService"
68
+ # soap.action = "GetUserById"
69
+ # soap.input = "GetUserByIdRequest"
70
+ # soap.body = { :id => 123 }
71
+ # end
72
+ class WSDL
73
+
74
+ # Expects a Savon::Request and accepts a custom +soap_endpoint+.
75
+ def initialize(request, soap_endpoint = nil)
76
+ @request, @enabled, @soap_endpoint = request, true, soap_endpoint
77
+ end
78
+
79
+ # Sets whether to use the WSDL.
80
+ attr_writer :enabled
81
+
82
+ # Returns whether to use the WSDL. Defaults to +true+.
83
+ def enabled?
84
+ @enabled
85
+ end
86
+
87
+ # Returns the namespace URI of the WSDL.
88
+ def namespace_uri
89
+ @namespace_uri ||= stream.namespace_uri
90
+ end
91
+
92
+ # Returns an Array of available SOAP actions.
93
+ def soap_actions
94
+ @soap_actions ||= stream.operations.keys
95
+ end
96
+
97
+ # Returns a Hash of SOAP operations including their corresponding
98
+ # SOAP actions and inputs.
99
+ def operations
100
+ @operations ||= stream.operations
101
+ end
102
+
103
+ # Returns the SOAP endpoint.
104
+ def soap_endpoint
105
+ @soap_endpoint ||= stream.soap_endpoint
106
+ end
107
+
108
+ # Returns +true+ for available methods and SOAP actions.
109
+ def respond_to?(method)
110
+ return true if !enabled? || soap_actions.include?(method)
111
+ super
112
+ end
113
+
114
+ # Returns an Array containg the SOAP action and input for a given +soap_call+.
115
+ def operation_from(soap_action)
116
+ return [soap_action.to_soap_key, soap_action.to_soap_key] unless enabled?
117
+ [operations[soap_action][:action], operations[soap_action][:input]]
118
+ end
119
+
120
+ # Returns the raw WSDL document.
121
+ def to_s
122
+ @document ||= @request.wsdl.body
123
+ end
124
+
125
+ private
126
+
127
+ # Returns the Savon::WSDLStream.
128
+ def stream
129
+ unless @stream
130
+ @stream = WSDLStream.new
131
+ REXML::Document.parse_stream to_s, @stream
132
+ end
133
+ @stream
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,85 @@
1
+ module Savon
2
+
3
+ # = Savon::WSDLStream
4
+ #
5
+ # Savon::WSDLStream serves as a stream listener for parsing the WSDL document.
6
+ class WSDLStream
7
+
8
+ # The main sections of a WSDL document.
9
+ Sections = %w(definitions types message portType binding service)
10
+
11
+ def initialize
12
+ @path, @operations, @namespaces = [], {}, {}
13
+ end
14
+
15
+ # Returns the namespace URI.
16
+ attr_reader :namespace_uri
17
+
18
+ # Returns the SOAP operations.
19
+ attr_reader :operations
20
+
21
+ # Returns the SOAP endpoint.
22
+ attr_reader :soap_endpoint
23
+
24
+ # Hook method called when the stream parser encounters a starting tag.
25
+ def tag_start(tag, attrs)
26
+ # read xml namespaces if root element
27
+ read_namespaces(attrs) if @path.empty?
28
+
29
+ tag, namespace = tag.split(":").reverse
30
+ @path << tag
31
+
32
+ if @section == :binding && tag == "binding"
33
+ # ensure that we are in an wsdl/soap namespace
34
+ @section = nil unless @namespaces[namespace].starts_with? "http://schemas.xmlsoap.org/wsdl/soap"
35
+ end
36
+
37
+ @section = tag.to_sym if Sections.include?(tag) && depth <= 2
38
+
39
+ @namespace_uri ||= attrs["targetNamespace"] if @section == :definitions
40
+ @soap_endpoint ||= URI(attrs["location"]) if @section == :service && tag == "address"
41
+
42
+ operation_from tag, attrs if @section == :binding && tag == "operation"
43
+ end
44
+
45
+ # Returns our current depth in the WSDL document.
46
+ def depth
47
+ @path.size
48
+ end
49
+
50
+ # Reads namespace definitions from a given +attrs+ Hash.
51
+ def read_namespaces(attrs)
52
+ attrs.each do |key, value|
53
+ @namespaces[key.strip_namespace] = value if key.starts_with? "xmlns:"
54
+ end
55
+ end
56
+
57
+ # Hook method called when the stream parser encounters a closing tag.
58
+ def tag_end(tag)
59
+ @path.pop
60
+
61
+ if @section == :binding && @input && tag.strip_namespace == "operation"
62
+ # no soapAction attribute found till now
63
+ operation_from tag, "soapAction" => @input
64
+ end
65
+ end
66
+
67
+ # Stores available operations from a given tag +name+ and +attrs+.
68
+ def operation_from(tag, attrs)
69
+ @input = attrs["name"] if attrs["name"]
70
+
71
+ if attrs["soapAction"]
72
+ @action = !attrs["soapAction"].blank? ? attrs["soapAction"] : @input
73
+ @input = @action.split("/").last if !@input || @input.empty?
74
+
75
+ @operations[@input.snakecase.to_sym] = { :action => @action, :input => @input }
76
+ @input, @action = nil, nil
77
+ end
78
+ end
79
+
80
+ # Catches calls to unimplemented hook methods.
81
+ def method_missing(method, *args)
82
+ end
83
+
84
+ end
85
+ end
data/lib/savon/wsse.rb ADDED
@@ -0,0 +1,163 @@
1
+ module Savon
2
+
3
+ # = Savon::WSSE
4
+ #
5
+ # Savon::WSSE represents WSSE authentication. Pass a block to your SOAP call and the WSSE object
6
+ # is passed to it as the second argument. The object allows setting the WSSE username, password
7
+ # and whether to use digest authentication.
8
+ #
9
+ # == Credentials
10
+ #
11
+ # By default, Savon does not use WSSE authentication. Simply specify a username and password to
12
+ # change this.
13
+ #
14
+ # response = client.get_all_users do |soap, wsse|
15
+ # wsse.username = "eve"
16
+ # wsse.password = "secret"
17
+ # end
18
+ #
19
+ # == Digest
20
+ #
21
+ # To use WSSE digest authentication, just use the digest method and set it to +true+.
22
+ #
23
+ # response = client.get_all_users do |soap, wsse|
24
+ # wsse.username = "eve"
25
+ # wsse.password = "secret"
26
+ # wsse.digest = true
27
+ # end
28
+ #
29
+ # == Default to WSSE
30
+ #
31
+ # In case all you're services require WSSE authentication, you can set your credentials and whether
32
+ # to use WSSE digest for every request:
33
+ #
34
+ # Savon::WSSE.username = "eve"
35
+ # Savon::WSSE.password = "secret"
36
+ # Savon::WSSE.digest = true
37
+ class WSSE
38
+
39
+ # Base address for WSSE docs.
40
+ BaseAddress = "http://docs.oasis-open.org/wss/2004/01"
41
+
42
+ # Namespace for WS Security Secext.
43
+ WSENamespace = "#{BaseAddress}/oasis-200401-wss-wssecurity-secext-1.0.xsd"
44
+
45
+ # Namespace for WS Security Utility.
46
+ WSUNamespace = "#{BaseAddress}/oasis-200401-wss-wssecurity-utility-1.0.xsd"
47
+
48
+ # URI for "wsse:Password/@Type" #PasswordText.
49
+ PasswordTextURI = "#{BaseAddress}/oasis-200401-wss-username-token-profile-1.0#PasswordText"
50
+
51
+ # URI for "wsse:Password/@Type" #PasswordDigest.
52
+ PasswordDigestURI = "#{BaseAddress}/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
53
+
54
+ # Global WSSE username.
55
+ @@username = nil
56
+
57
+ # Returns the global WSSE username.
58
+ def self.username
59
+ @@username
60
+ end
61
+
62
+ # Sets the global WSSE username.
63
+ def self.username=(username)
64
+ @@username = username.nil? ? nil : username.to_s
65
+ end
66
+
67
+ # Global WSSE password.
68
+ @@password = nil
69
+
70
+ # Returns the global WSSE password.
71
+ def self.password
72
+ @@password
73
+ end
74
+
75
+ # Sets the global WSSE password.
76
+ def self.password=(password)
77
+ @@password = password.nil? ? nil : password.to_s
78
+ end
79
+
80
+ # Global setting of whether to use WSSE digest.
81
+ @@digest = false
82
+
83
+ # Returns the global setting of whether to use WSSE digest.
84
+ def self.digest?
85
+ @@digest
86
+ end
87
+
88
+ # Global setting of whether to use WSSE digest.
89
+ def self.digest=(digest)
90
+ @@digest = digest
91
+ end
92
+
93
+ # Sets the WSSE username per request.
94
+ def username=(username)
95
+ @username = username.nil? ? nil : username.to_s
96
+ end
97
+
98
+ # Returns the WSSE username. Defaults to the global setting.
99
+ def username
100
+ @username || self.class.username
101
+ end
102
+
103
+ # Sets the WSSE password per request.
104
+ def password=(password)
105
+ @password = password.nil? ? nil : password.to_s
106
+ end
107
+
108
+ # Returns the WSSE password. Defaults to the global setting.
109
+ def password
110
+ @password || self.class.password
111
+ end
112
+
113
+ # Sets whether to use WSSE digest per request.
114
+ attr_writer :digest
115
+
116
+ # Returns whether to use WSSE digest. Defaults to the global setting.
117
+ def digest?
118
+ @digest || self.class.digest?
119
+ end
120
+
121
+ # Returns the XML for a WSSE header or an empty String unless both username and password
122
+ # were specified.
123
+ def header
124
+ return "" unless username && password
125
+
126
+ builder = Builder::XmlMarkup.new
127
+ builder.wsse :Security, "xmlns:wsse" => WSENamespace do |xml|
128
+ xml.wsse :UsernameToken, "xmlns:wsu" => WSUNamespace do
129
+ xml.wsse :Username, username
130
+ xml.wsse :Nonce, nonce
131
+ xml.wsu :Created, timestamp
132
+ xml.wsse :Password, password_node, :Type => password_type
133
+ end
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ # Returns the WSSE password. Encrypts the password for digest authentication.
140
+ def password_node
141
+ return password unless digest?
142
+
143
+ token = nonce + timestamp + password
144
+ Base64.encode64(Digest::SHA1.hexdigest(token)).chomp!
145
+ end
146
+
147
+ # Returns the URI for the "wsse:Password/@Type" attribute.
148
+ def password_type
149
+ digest? ? PasswordDigestURI : PasswordTextURI
150
+ end
151
+
152
+ # Returns a WSSE nonce.
153
+ def nonce
154
+ @nonce ||= Digest::SHA1.hexdigest String.random + timestamp
155
+ end
156
+
157
+ # Returns a WSSE timestamp.
158
+ def timestamp
159
+ @timestamp ||= Time.now.strftime Savon::SOAP::DateTimeFormat
160
+ end
161
+
162
+ end
163
+ end
@@ -0,0 +1,11 @@
1
+ require "rake"
2
+ require "spec"
3
+ require "mocha"
4
+ require "fakeweb"
5
+
6
+ Spec::Runner.configure do |config|
7
+ config.mock_with :mocha
8
+ end
9
+
10
+ require "savon"
11
+ Savon::Request.log = false
@@ -0,0 +1,23 @@
1
+ class EndpointHelper
2
+
3
+ # Returns the WSDL endpoint for a given +type+ of request.
4
+ def self.wsdl_endpoint(type = nil)
5
+ case type
6
+ when :no_namespace then "http://nons.example.com/Service?wsdl"
7
+ when :namespaced_actions then "http://nsactions.example.com/Service?wsdl"
8
+ when :geotrust then "https://test-api.geotrust.com/webtrust/query.jws?WSDL"
9
+ else soap_endpoint(type)
10
+ end
11
+ end
12
+
13
+ # Returns the SOAP endpoint for a given +type+ of request.
14
+ def self.soap_endpoint(type = nil)
15
+ case type
16
+ when :soap_fault then "http://soapfault.example.com/Service?wsdl"
17
+ when :http_error then "http://httperror.example.com/Service?wsdl"
18
+ when :invalid then "http://invalid.example.com/Service?wsdl"
19
+ else "http://example.com/validation/1.0/AuthenticationService"
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,7 @@
1
+ class GzipResponseFixture
2
+
3
+ def self.message
4
+ File.read(File.join(File.dirname(__FILE__), 'message.gz'))
5
+ end
6
+ end
7
+
Binary file
@@ -0,0 +1,36 @@
1
+ class ResponseFixture
2
+
3
+ def self.authentication(value = nil)
4
+ case value
5
+ when :to_hash
6
+ { :success => true,
7
+ :authentication_value => {
8
+ :token => "a68d1d6379b62ff339a0e0c69ed4d9cf",
9
+ :token_hash => "AAAJxA;cIedoT;mY10ExZwG6JuKgp2OYKxow==",
10
+ :client => "radclient"
11
+ }
12
+ }
13
+ else
14
+ @@authentication ||= load_fixture :authentication
15
+ end
16
+ end
17
+
18
+ def self.soap_fault
19
+ @@soap_fault ||= load_fixture :soap_fault
20
+ end
21
+
22
+ def self.soap_fault12
23
+ @@soap_fault12 ||= load_fixture :soap_fault12
24
+ end
25
+
26
+ def self.multi_ref
27
+ @@multi_ref ||= load_fixture :multi_ref
28
+ end
29
+
30
+ private
31
+
32
+ def self.load_fixture(fixture)
33
+ File.read File.dirname(__FILE__) + "/xml/#{fixture}.xml"
34
+ end
35
+
36
+ end