s-savon 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +461 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +40 -0
- data/lib/savon.rb +14 -0
- data/lib/savon/client.rb +157 -0
- data/lib/savon/core_ext/hash.rb +70 -0
- data/lib/savon/core_ext/object.rb +14 -0
- data/lib/savon/core_ext/string.rb +51 -0
- data/lib/savon/core_ext/time.rb +14 -0
- data/lib/savon/error.rb +6 -0
- data/lib/savon/global.rb +75 -0
- data/lib/savon/http/error.rb +42 -0
- data/lib/savon/soap.rb +24 -0
- data/lib/savon/soap/fault.rb +59 -0
- data/lib/savon/soap/request.rb +61 -0
- data/lib/savon/soap/response.rb +80 -0
- data/lib/savon/soap/xml.rb +187 -0
- data/lib/savon/version.rb +5 -0
- data/lib/savon/wsdl/document.rb +112 -0
- data/lib/savon/wsdl/parser.rb +102 -0
- data/lib/savon/wsdl/request.rb +35 -0
- data/lib/savon/wsse.rb +150 -0
- data/savon.gemspec +29 -0
- data/spec/fixtures/gzip/message.gz +0 -0
- data/spec/fixtures/response/another_soap_fault.xml +14 -0
- data/spec/fixtures/response/authentication.xml +14 -0
- data/spec/fixtures/response/header.xml +13 -0
- data/spec/fixtures/response/list.xml +18 -0
- data/spec/fixtures/response/multi_ref.xml +39 -0
- data/spec/fixtures/response/soap_fault.xml +8 -0
- data/spec/fixtures/response/soap_fault12.xml +18 -0
- data/spec/fixtures/wsdl/authentication.xml +63 -0
- data/spec/fixtures/wsdl/geotrust.xml +156 -0
- data/spec/fixtures/wsdl/namespaced_actions.xml +307 -0
- data/spec/fixtures/wsdl/no_namespace.xml +115 -0
- data/spec/fixtures/wsdl/two_bindings.xml +25 -0
- data/spec/savon/client_spec.rb +346 -0
- data/spec/savon/core_ext/hash_spec.rb +121 -0
- data/spec/savon/core_ext/object_spec.rb +19 -0
- data/spec/savon/core_ext/string_spec.rb +57 -0
- data/spec/savon/core_ext/time_spec.rb +13 -0
- data/spec/savon/http/error_spec.rb +52 -0
- data/spec/savon/savon_spec.rb +85 -0
- data/spec/savon/soap/fault_spec.rb +89 -0
- data/spec/savon/soap/request_spec.rb +45 -0
- data/spec/savon/soap/response_spec.rb +174 -0
- data/spec/savon/soap/xml_spec.rb +335 -0
- data/spec/savon/soap_spec.rb +21 -0
- data/spec/savon/wsdl/document_spec.rb +132 -0
- data/spec/savon/wsdl/parser_spec.rb +99 -0
- data/spec/savon/wsdl/request_spec.rb +15 -0
- data/spec/savon/wsse_spec.rb +213 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/endpoint.rb +25 -0
- data/spec/support/fixture.rb +37 -0
- metadata +251 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
|
3
|
+
require "savon/wsdl/request"
|
4
|
+
require "savon/wsdl/parser"
|
5
|
+
|
6
|
+
module Savon
|
7
|
+
module WSDL
|
8
|
+
|
9
|
+
# = Savon::WSDL::Document
|
10
|
+
#
|
11
|
+
# Represents the WSDL of your service, including information like the namespace URI,
|
12
|
+
# the SOAP endpoint and available SOAP actions.
|
13
|
+
class Document
|
14
|
+
|
15
|
+
# Accepts an <tt>HTTPI::Request</tt> and a +document+.
|
16
|
+
def initialize(request = nil, document = nil)
|
17
|
+
self.request = request
|
18
|
+
self.document = document
|
19
|
+
end
|
20
|
+
|
21
|
+
# Accessor for the <tt>HTTPI::Request</tt> to use.
|
22
|
+
attr_accessor :request
|
23
|
+
|
24
|
+
def present?
|
25
|
+
!!@document
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the namespace URI of the WSDL.
|
29
|
+
def namespace
|
30
|
+
@namespace ||= parser.namespace
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets the SOAP namespace.
|
34
|
+
attr_writer :namespace
|
35
|
+
|
36
|
+
# Returns the SOAP endpoint.
|
37
|
+
def endpoint
|
38
|
+
@endpoint ||= parser.endpoint
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets the SOAP endpoint.
|
42
|
+
attr_writer :endpoint
|
43
|
+
|
44
|
+
# Returns an Array of available SOAP actions.
|
45
|
+
def soap_actions
|
46
|
+
@soap_actions ||= parser.operations.keys
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the SOAP action for a given +key+.
|
50
|
+
def soap_action(key)
|
51
|
+
operations[key][:action] if present? && operations[key]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the SOAP input for a given +key+.
|
55
|
+
def soap_input(key)
|
56
|
+
operations[key][:input].to_sym if present? && operations[key]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns a Hash of SOAP operations.
|
60
|
+
def operations
|
61
|
+
@operations ||= parser.operations
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the elementFormDefault value.
|
65
|
+
def element_form_default
|
66
|
+
@element_form_default ||= parser.element_form_default
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the location of the WSDL document to use. This can either be a URL
|
70
|
+
# or a path to a local file.
|
71
|
+
attr_writer :document
|
72
|
+
|
73
|
+
# Returns the raw WSDL document.
|
74
|
+
def document
|
75
|
+
@wsdl_document ||= begin
|
76
|
+
raise ArgumentError, "No WSDL document given" if @document.blank?
|
77
|
+
remote? ? http_request : read_file
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
alias :to_xml :document
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Returns whether the WSDL document is located on the Web.
|
86
|
+
def remote?
|
87
|
+
@document =~ /^http/
|
88
|
+
end
|
89
|
+
|
90
|
+
# Executes an HTTP GET request to retrieve a remote WSDL document.
|
91
|
+
def http_request
|
92
|
+
request.url = @document
|
93
|
+
Request.new(request).response.body
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reads the WSDL document from a local file.
|
97
|
+
def read_file
|
98
|
+
File.read @document
|
99
|
+
end
|
100
|
+
|
101
|
+
# Parses the WSDL document and returns the <tt>Savon::WSDL::Parser</tt>.
|
102
|
+
def parser
|
103
|
+
@parser ||= begin
|
104
|
+
parser = Parser.new
|
105
|
+
REXML::Document.parse_stream document, parser
|
106
|
+
parser
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "savon/core_ext/object"
|
2
|
+
require "savon/core_ext/string"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module WSDL
|
6
|
+
|
7
|
+
# = Savon::WSDL::Parser
|
8
|
+
#
|
9
|
+
# Serves as a stream listener for parsing WSDL documents.
|
10
|
+
class Parser
|
11
|
+
|
12
|
+
# The main sections of a WSDL document.
|
13
|
+
Sections = %w(definitions types message portType binding service)
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@path = []
|
17
|
+
@operations = {}
|
18
|
+
@namespaces = {}
|
19
|
+
@element_form_default = :unqualified
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the namespace URI.
|
23
|
+
attr_reader :namespace
|
24
|
+
|
25
|
+
# Returns the SOAP operations.
|
26
|
+
attr_reader :operations
|
27
|
+
|
28
|
+
# Returns the SOAP endpoint.
|
29
|
+
attr_reader :endpoint
|
30
|
+
|
31
|
+
# Returns the elementFormDefault value.
|
32
|
+
attr_reader :element_form_default
|
33
|
+
|
34
|
+
# Hook method called when the stream parser encounters a starting tag.
|
35
|
+
def tag_start(tag, attrs)
|
36
|
+
# read xml namespaces if root element
|
37
|
+
read_namespaces(attrs) if @path.empty?
|
38
|
+
|
39
|
+
tag, namespace = tag.split(":").reverse
|
40
|
+
@path << tag
|
41
|
+
|
42
|
+
if @section == :types && tag == "schema"
|
43
|
+
@element_form_default = attrs["elementFormDefault"].to_sym if attrs["elementFormDefault"]
|
44
|
+
end
|
45
|
+
|
46
|
+
if @section == :binding && tag == "binding"
|
47
|
+
# ensure that we are in an wsdl/soap namespace
|
48
|
+
@section = nil unless @namespaces[namespace].starts_with? "http://schemas.xmlsoap.org/wsdl/soap"
|
49
|
+
end
|
50
|
+
|
51
|
+
@section = tag.to_sym if Sections.include?(tag) && depth <= 2
|
52
|
+
|
53
|
+
@namespace ||= attrs["targetNamespace"] if @section == :definitions
|
54
|
+
@endpoint ||= URI(URI.escape(attrs["location"])) if @section == :service && tag == "address"
|
55
|
+
|
56
|
+
operation_from tag, attrs if @section == :binding && tag == "operation"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns our current depth in the WSDL document.
|
60
|
+
def depth
|
61
|
+
@path.size
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reads namespace definitions from a given +attrs+ Hash.
|
65
|
+
def read_namespaces(attrs)
|
66
|
+
attrs.each do |key, value|
|
67
|
+
@namespaces[key.strip_namespace] = value if key.starts_with? "xmlns:"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Hook method called when the stream parser encounters a closing tag.
|
72
|
+
def tag_end(tag)
|
73
|
+
@path.pop
|
74
|
+
|
75
|
+
if @section == :binding && @input && tag.strip_namespace == "operation"
|
76
|
+
# no soapAction attribute found till now
|
77
|
+
operation_from tag, "soapAction" => @input
|
78
|
+
end
|
79
|
+
|
80
|
+
@section = :definitions if Sections.include?(tag) && depth <= 1
|
81
|
+
end
|
82
|
+
|
83
|
+
# Stores available operations from a given tag +name+ and +attrs+.
|
84
|
+
def operation_from(tag, attrs)
|
85
|
+
@input = attrs["name"] if attrs["name"]
|
86
|
+
|
87
|
+
if attrs["soapAction"]
|
88
|
+
@action = !attrs["soapAction"].blank? ? attrs["soapAction"] : @input
|
89
|
+
@input = @action.split("/").last if !@input || @input.empty?
|
90
|
+
|
91
|
+
@operations[@input.snakecase.to_sym] = { :action => @action, :input => @input }
|
92
|
+
@input, @action = nil, nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Catches calls to unimplemented hook methods.
|
97
|
+
def method_missing(method, *args)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "httpi"
|
2
|
+
|
3
|
+
module Savon
|
4
|
+
module WSDL
|
5
|
+
|
6
|
+
# = Savon::WSDL::Request
|
7
|
+
#
|
8
|
+
# Executes WSDL requests.
|
9
|
+
class Request
|
10
|
+
|
11
|
+
# Expects an <tt>HTTPI::Request</tt>.
|
12
|
+
def initialize(request)
|
13
|
+
self.request = request
|
14
|
+
end
|
15
|
+
|
16
|
+
# Accessor for the <tt>HTTPI::Request</tt>.
|
17
|
+
attr_accessor :request
|
18
|
+
|
19
|
+
# Executes the request and returns the response.
|
20
|
+
def response
|
21
|
+
@response ||= with_logging { HTTPI.get request }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Logs the HTTP request and yields to a given +block+.
|
27
|
+
def with_logging
|
28
|
+
Savon.log "Retrieving WSDL from: #{request.url}"
|
29
|
+
Savon.log "Using :#{request.auth.type} authentication" if request.auth?
|
30
|
+
yield
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/savon/wsse.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "digest/sha1"
|
3
|
+
|
4
|
+
require "savon/core_ext/string"
|
5
|
+
require "savon/core_ext/hash"
|
6
|
+
require "savon/core_ext/time"
|
7
|
+
|
8
|
+
module Savon
|
9
|
+
|
10
|
+
# = Savon::WSSE
|
11
|
+
#
|
12
|
+
# Provides WSSE authentication.
|
13
|
+
class WSSE
|
14
|
+
|
15
|
+
# Namespace for WS Security Secext.
|
16
|
+
WSENamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
|
17
|
+
|
18
|
+
# Namespace for WS Security Utility.
|
19
|
+
WSUNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
20
|
+
|
21
|
+
# URI for "wsse:Password/@Type" #PasswordText.
|
22
|
+
PasswordTextURI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"
|
23
|
+
|
24
|
+
# URI for "wsse:Password/@Type" #PasswordDigest.
|
25
|
+
PasswordDigestURI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
|
26
|
+
|
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.
|
39
|
+
def credentials(username, password, digest = false)
|
40
|
+
self.username = username
|
41
|
+
self.password = password
|
42
|
+
self.digest = digest
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :username, :password, :created_at, :expires_at
|
46
|
+
|
47
|
+
# Returns whether to use WSSE digest. Defaults to +false+.
|
48
|
+
def digest?
|
49
|
+
!!@digest
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_writer :digest
|
53
|
+
|
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.
|
70
|
+
def to_xml
|
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
|
+
""
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Returns a Hash containing wsse:UsernameToken details.
|
83
|
+
def wsse_username_token
|
84
|
+
if digest?
|
85
|
+
wsse_security "UsernameToken",
|
86
|
+
"wsse:Username" => username,
|
87
|
+
"wsse:Nonce" => nonce,
|
88
|
+
"wsu:Created" => timestamp,
|
89
|
+
"wsse:Password" => digest_password,
|
90
|
+
:attributes! => { "wsse:Password" => { "Type" => PasswordDigestURI } }
|
91
|
+
else
|
92
|
+
wsse_security "UsernameToken",
|
93
|
+
"wsse:Username" => username,
|
94
|
+
"wsse:Password" => password,
|
95
|
+
:attributes! => { "wsse:Password" => { "Type" => PasswordTextURI } }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns a Hash containing wsse:Timestamp details.
|
100
|
+
def wsse_timestamp
|
101
|
+
wsse_security "Timestamp",
|
102
|
+
"wsu:Created" => (created_at || Time.now).xs_datetime,
|
103
|
+
"wsu:Expires" => (expires_at || (created_at || Time.now) + 60).xs_datetime
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a Hash containing wsse:Security details for a given +tag+ and +hash+.
|
107
|
+
def wsse_security(tag, hash)
|
108
|
+
{
|
109
|
+
"wsse:Security" => {
|
110
|
+
"wsse:#{tag}" => hash,
|
111
|
+
:attributes! => { "wsse:#{tag}" => { "wsu:Id" => "#{tag}-#{count}", "xmlns:wsu" => WSUNamespace } }
|
112
|
+
},
|
113
|
+
:attributes! => { "wsse:Security" => { "xmlns:wsse" => WSENamespace } }
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the WSSE password, encrypted for digest authentication.
|
118
|
+
def digest_password
|
119
|
+
token = nonce + timestamp + password
|
120
|
+
Base64.encode64(Digest::SHA1.hexdigest(token)).chomp!
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns a WSSE nonce.
|
124
|
+
def nonce
|
125
|
+
@nonce ||= Digest::SHA1.hexdigest random_string + timestamp
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a random String of 100 characters.
|
129
|
+
def random_string
|
130
|
+
(0...100).map { ("a".."z").to_a[rand(26)] }.join
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns a WSSE timestamp.
|
134
|
+
def timestamp
|
135
|
+
@timestamp ||= Time.now.xs_datetime
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns a new number with every call.
|
139
|
+
def count
|
140
|
+
@count ||= 0
|
141
|
+
@count += 1
|
142
|
+
end
|
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
|
+
|
149
|
+
end
|
150
|
+
end
|
data/savon.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$:.unshift lib unless $:.include? lib
|
3
|
+
|
4
|
+
require "savon/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "s-savon"
|
8
|
+
s.version = Savon::Version
|
9
|
+
s.authors = "Daniel Harrington"
|
10
|
+
s.email = "me@rubiii.com"
|
11
|
+
s.homepage = "http://github.com/rubiii/#{s.name}"
|
12
|
+
s.summary = "Heavy metal Ruby SOAP client"
|
13
|
+
s.description = "Savon is the heavy metal Ruby SOAP client."
|
14
|
+
|
15
|
+
s.rubyforge_project = s.name
|
16
|
+
|
17
|
+
s.add_dependency "builder", ">= 2.1.2"
|
18
|
+
s.add_dependency "crack", "~> 0.1.8"
|
19
|
+
s.add_dependency "httpi", ">= 0.7.8"
|
20
|
+
s.add_dependency "gyoku", ">= 0.3.0"
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec", "~> 2.4.0"
|
23
|
+
s.add_development_dependency "autotest"
|
24
|
+
s.add_development_dependency "mocha", "~> 0.9.8"
|
25
|
+
s.add_development_dependency "timecop", "~> 0.3.5"
|
26
|
+
|
27
|
+
s.files = `git ls-files`.split("\n")
|
28
|
+
s.require_path = "lib"
|
29
|
+
end
|
Binary file
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
|
2
|
+
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
3
|
+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
|
4
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
5
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
6
|
+
<SOAP-ENV:Body>
|
7
|
+
<SOAP-ENV:Fault>
|
8
|
+
<faultcode>ERR_NO_SESSION</faultcode>
|
9
|
+
<faultfactor>doGetItemsInfo - Wrong session</faultfactor>
|
10
|
+
<faultstring>Wrong session message</faultstring>
|
11
|
+
<detail><soapVal><ERRNO xsi:type="xsd:string">80:1289245853:55</ERRNO></soapVal></detail>
|
12
|
+
</SOAP-ENV:Fault>
|
13
|
+
</SOAP-ENV:Body>
|
14
|
+
</SOAP-ENV:Envelope>
|