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