s-savon 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +9 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +2 -0
  4. data/CHANGELOG.md +461 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +37 -0
  8. data/Rakefile +40 -0
  9. data/lib/savon.rb +14 -0
  10. data/lib/savon/client.rb +157 -0
  11. data/lib/savon/core_ext/hash.rb +70 -0
  12. data/lib/savon/core_ext/object.rb +14 -0
  13. data/lib/savon/core_ext/string.rb +51 -0
  14. data/lib/savon/core_ext/time.rb +14 -0
  15. data/lib/savon/error.rb +6 -0
  16. data/lib/savon/global.rb +75 -0
  17. data/lib/savon/http/error.rb +42 -0
  18. data/lib/savon/soap.rb +24 -0
  19. data/lib/savon/soap/fault.rb +59 -0
  20. data/lib/savon/soap/request.rb +61 -0
  21. data/lib/savon/soap/response.rb +80 -0
  22. data/lib/savon/soap/xml.rb +187 -0
  23. data/lib/savon/version.rb +5 -0
  24. data/lib/savon/wsdl/document.rb +112 -0
  25. data/lib/savon/wsdl/parser.rb +102 -0
  26. data/lib/savon/wsdl/request.rb +35 -0
  27. data/lib/savon/wsse.rb +150 -0
  28. data/savon.gemspec +29 -0
  29. data/spec/fixtures/gzip/message.gz +0 -0
  30. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  31. data/spec/fixtures/response/authentication.xml +14 -0
  32. data/spec/fixtures/response/header.xml +13 -0
  33. data/spec/fixtures/response/list.xml +18 -0
  34. data/spec/fixtures/response/multi_ref.xml +39 -0
  35. data/spec/fixtures/response/soap_fault.xml +8 -0
  36. data/spec/fixtures/response/soap_fault12.xml +18 -0
  37. data/spec/fixtures/wsdl/authentication.xml +63 -0
  38. data/spec/fixtures/wsdl/geotrust.xml +156 -0
  39. data/spec/fixtures/wsdl/namespaced_actions.xml +307 -0
  40. data/spec/fixtures/wsdl/no_namespace.xml +115 -0
  41. data/spec/fixtures/wsdl/two_bindings.xml +25 -0
  42. data/spec/savon/client_spec.rb +346 -0
  43. data/spec/savon/core_ext/hash_spec.rb +121 -0
  44. data/spec/savon/core_ext/object_spec.rb +19 -0
  45. data/spec/savon/core_ext/string_spec.rb +57 -0
  46. data/spec/savon/core_ext/time_spec.rb +13 -0
  47. data/spec/savon/http/error_spec.rb +52 -0
  48. data/spec/savon/savon_spec.rb +85 -0
  49. data/spec/savon/soap/fault_spec.rb +89 -0
  50. data/spec/savon/soap/request_spec.rb +45 -0
  51. data/spec/savon/soap/response_spec.rb +174 -0
  52. data/spec/savon/soap/xml_spec.rb +335 -0
  53. data/spec/savon/soap_spec.rb +21 -0
  54. data/spec/savon/wsdl/document_spec.rb +132 -0
  55. data/spec/savon/wsdl/parser_spec.rb +99 -0
  56. data/spec/savon/wsdl/request_spec.rb +15 -0
  57. data/spec/savon/wsse_spec.rb +213 -0
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/endpoint.rb +25 -0
  60. data/spec/support/fixture.rb +37 -0
  61. metadata +251 -0
@@ -0,0 +1,5 @@
1
+ module Savon
2
+
3
+ Version = "0.8.6"
4
+
5
+ end
@@ -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
@@ -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
@@ -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
@@ -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>