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.
- data/.autotest +5 -0
- data/CHANGELOG +176 -0
- data/LICENSE +20 -0
- data/README.rdoc +64 -0
- data/Rakefile +50 -0
- data/lib/savon.rb +35 -0
- data/lib/savon/client.rb +131 -0
- data/lib/savon/core_ext.rb +8 -0
- data/lib/savon/core_ext/array.rb +31 -0
- data/lib/savon/core_ext/datetime.rb +10 -0
- data/lib/savon/core_ext/hash.rb +107 -0
- data/lib/savon/core_ext/net_http.rb +19 -0
- data/lib/savon/core_ext/object.rb +16 -0
- data/lib/savon/core_ext/string.rb +69 -0
- data/lib/savon/core_ext/symbol.rb +8 -0
- data/lib/savon/core_ext/uri.rb +10 -0
- data/lib/savon/logger.rb +56 -0
- data/lib/savon/request.rb +138 -0
- data/lib/savon/response.rb +174 -0
- data/lib/savon/soap.rb +302 -0
- data/lib/savon/version.rb +5 -0
- data/lib/savon/wsdl.rb +137 -0
- data/lib/savon/wsdl_stream.rb +85 -0
- data/lib/savon/wsse.rb +163 -0
- data/spec/basic_spec_helper.rb +11 -0
- data/spec/endpoint_helper.rb +23 -0
- data/spec/fixtures/gzip/gzip_response_fixture.rb +7 -0
- data/spec/fixtures/gzip/message.gz +0 -0
- data/spec/fixtures/response/response_fixture.rb +36 -0
- data/spec/fixtures/response/xml/authentication.xml +14 -0
- data/spec/fixtures/response/xml/multi_ref.xml +39 -0
- data/spec/fixtures/response/xml/soap_fault.xml +8 -0
- data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
- data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
- data/spec/fixtures/wsdl/wsdl_fixture.yml +42 -0
- data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
- data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
- data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
- data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
- data/spec/http_stubs.rb +26 -0
- data/spec/integration/http_basic_auth_spec.rb +16 -0
- data/spec/integration/server.rb +51 -0
- data/spec/savon/client_spec.rb +86 -0
- data/spec/savon/core_ext/array_spec.rb +49 -0
- data/spec/savon/core_ext/datetime_spec.rb +21 -0
- data/spec/savon/core_ext/hash_spec.rb +190 -0
- data/spec/savon/core_ext/net_http_spec.rb +38 -0
- data/spec/savon/core_ext/object_spec.rb +34 -0
- data/spec/savon/core_ext/string_spec.rb +99 -0
- data/spec/savon/core_ext/symbol_spec.rb +12 -0
- data/spec/savon/core_ext/uri_spec.rb +19 -0
- data/spec/savon/request_spec.rb +117 -0
- data/spec/savon/response_spec.rb +179 -0
- data/spec/savon/soap_spec.rb +202 -0
- data/spec/savon/wsdl_spec.rb +107 -0
- data/spec/savon/wsse_spec.rb +132 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +5 -0
- metadata +229 -0
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,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
|
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
|