savon-xaop 0.7.2.1

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 (48) hide show
  1. data/CHANGELOG +124 -0
  2. data/README.textile +75 -0
  3. data/Rakefile +45 -0
  4. data/lib/savon/client.rb +84 -0
  5. data/lib/savon/core_ext/datetime.rb +8 -0
  6. data/lib/savon/core_ext/hash.rb +78 -0
  7. data/lib/savon/core_ext/net_http.rb +20 -0
  8. data/lib/savon/core_ext/object.rb +21 -0
  9. data/lib/savon/core_ext/string.rb +47 -0
  10. data/lib/savon/core_ext/symbol.rb +8 -0
  11. data/lib/savon/core_ext/uri.rb +10 -0
  12. data/lib/savon/core_ext.rb +3 -0
  13. data/lib/savon/request.rb +160 -0
  14. data/lib/savon/response.rb +108 -0
  15. data/lib/savon/soap.rb +176 -0
  16. data/lib/savon/wsdl.rb +122 -0
  17. data/lib/savon/wsse.rb +136 -0
  18. data/lib/savon.rb +34 -0
  19. data/spec/basic_spec_helper.rb +12 -0
  20. data/spec/endpoint_helper.rb +22 -0
  21. data/spec/fixtures/response/response_fixture.rb +36 -0
  22. data/spec/fixtures/response/xml/authentication.xml +14 -0
  23. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  24. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  25. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  26. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  27. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  28. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  29. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  30. data/spec/http_stubs.rb +23 -0
  31. data/spec/integration/http_basic_auth_spec.rb +16 -0
  32. data/spec/integration/server.rb +51 -0
  33. data/spec/savon/client_spec.rb +77 -0
  34. data/spec/savon/core_ext/datetime_spec.rb +12 -0
  35. data/spec/savon/core_ext/hash_spec.rb +138 -0
  36. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  37. data/spec/savon/core_ext/object_spec.rb +40 -0
  38. data/spec/savon/core_ext/string_spec.rb +68 -0
  39. data/spec/savon/core_ext/symbol_spec.rb +11 -0
  40. data/spec/savon/core_ext/uri_spec.rb +15 -0
  41. data/spec/savon/request_spec.rb +89 -0
  42. data/spec/savon/response_spec.rb +137 -0
  43. data/spec/savon/savon_spec.rb +23 -0
  44. data/spec/savon/soap_spec.rb +171 -0
  45. data/spec/savon/wsdl_spec.rb +84 -0
  46. data/spec/savon/wsse_spec.rb +132 -0
  47. data/spec/spec_helper.rb +5 -0
  48. metadata +175 -0
@@ -0,0 +1,160 @@
1
+ module Savon
2
+
3
+ # == Savon::Request
4
+ #
5
+ # Handles both WSDL and SOAP HTTP requests.
6
+ class Request
7
+
8
+ # Content-Types by SOAP version.
9
+ ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
10
+
11
+ # Whether to log HTTP requests.
12
+ @@log = true
13
+
14
+ # The default logger.
15
+ @@logger = Logger.new STDOUT
16
+
17
+ # The default log level.
18
+ @@log_level = :debug
19
+
20
+ # Sets whether to log HTTP requests.
21
+ def self.log=(log)
22
+ @@log = log
23
+ end
24
+
25
+ # Returns whether to log HTTP requests.
26
+ def self.log?
27
+ @@log
28
+ end
29
+
30
+ # Sets the logger.
31
+ def self.logger=(logger)
32
+ @@logger = logger
33
+ end
34
+
35
+ # Returns the logger.
36
+ def self.logger
37
+ @@logger
38
+ end
39
+
40
+ # Sets the log level.
41
+ def self.log_level=(log_level)
42
+ @@log_level = log_level
43
+ end
44
+
45
+ # Returns the log level.
46
+ def self.log_level
47
+ @@log_level
48
+ end
49
+
50
+ # Expects a SOAP +endpoint+ String. Also accepts an optional Hash
51
+ # of +options+ for specifying a proxy server.
52
+ def initialize(endpoint, options = {})
53
+ @endpoint = URI endpoint
54
+ @proxy = options[:proxy] ? URI(options[:proxy]) : URI("")
55
+ end
56
+
57
+ # Returns the endpoint URI.
58
+ attr_reader :endpoint
59
+
60
+ # Returns the proxy URI.
61
+ attr_reader :proxy
62
+
63
+ # Returns the HTTP headers for a SOAP request.
64
+ def headers
65
+ @headers ||= {}
66
+ end
67
+
68
+ # Sets the HTTP headers for a SOAP request.
69
+ def headers=(headers)
70
+ @headers = headers if headers.kind_of? Hash
71
+ end
72
+
73
+ # Sets the +username+ and +password+ for HTTP basic authentication.
74
+ def basic_auth(username, password)
75
+ @basic_auth = [username, password]
76
+ end
77
+
78
+ # Sets the +username+ and +password+ for NTLM authentication.
79
+ def ntlm_auth(username, password)
80
+ @ntlm_auth = [username, password]
81
+ end
82
+
83
+ # Retrieves WSDL document and returns the Net::HTTP response.
84
+ def wsdl
85
+ log "Retrieving WSDL from: #{@endpoint}"
86
+ http.endpoint @endpoint.host, @endpoint.port
87
+ http.use_ssl = @endpoint.ssl?
88
+ http.start { |h| h.request request(:wsdl) }
89
+ end
90
+
91
+ # Executes a SOAP request using a given Savon::SOAP instance and
92
+ # returns the Net::HTTP response.
93
+ def soap(soap)
94
+ @soap = soap
95
+ http.endpoint @soap.endpoint.host, @soap.endpoint.port
96
+ http.use_ssl = @soap.endpoint.ssl?
97
+
98
+ log_request
99
+ @response = http.start do |h|
100
+ h.request request(:soap) { |request| request.body = @soap.to_xml }
101
+ end
102
+ log_response
103
+ @response
104
+ end
105
+
106
+ # Returns the Net::HTTP object.
107
+ def http
108
+ @http ||= Net::HTTP::Proxy(@proxy.host, @proxy.port).new @endpoint.host, @endpoint.port
109
+ end
110
+
111
+ private
112
+
113
+ # Logs the SOAP request.
114
+ def log_request
115
+ log "SOAP request: #{@soap.endpoint}"
116
+ log headers.merge(soap_headers).map { |key, value| "#{key}: #{value}" }.join(", ")
117
+ log @soap.to_xml
118
+ end
119
+
120
+ # Logs the SOAP response.
121
+ def log_response
122
+ log "SOAP response (status #{@response.code}):"
123
+ log @response.body
124
+ end
125
+
126
+ # Returns a Net::HTTP request for a given +type+. Yields the request
127
+ # to an optional block.
128
+ def request(type)
129
+ request = case type
130
+ when :wsdl then Net::HTTP::Get.new @endpoint.to_s, headers
131
+ when :soap then Net::HTTP::Post.new @soap.endpoint.to_s, headers.merge(soap_headers)
132
+ end
133
+ if @basic_auth
134
+ request.basic_auth *@basic_auth
135
+ elsif @ntlm_auth
136
+ log "Using NTLM authentication"
137
+ require 'net/ntlm_http'
138
+ request.ntlm_auth *@ntlm_auth
139
+ end
140
+ yield request if block_given?
141
+ request
142
+ end
143
+
144
+ # Returns a Hash containing the SOAP headers for an HTTP request.
145
+ def soap_headers
146
+ { "Content-Type" => ContentType[@soap.version], "SOAPAction" => @soap.action }
147
+ end
148
+
149
+ # Logs a given +message+.
150
+ def log(message)
151
+ self.class.logger.send self.class.log_level, message if log?
152
+ end
153
+
154
+ # Returns whether to log.
155
+ def log?
156
+ self.class.log? && self.class.logger.respond_to?(self.class.log_level)
157
+ end
158
+
159
+ end
160
+ end
@@ -0,0 +1,108 @@
1
+ module Savon
2
+
3
+ # == Savon::Response
4
+ #
5
+ # Represents the HTTP and SOAP response.
6
+ class Response
7
+
8
+ # The global setting of whether to raise errors.
9
+ @@raise_errors = true
10
+
11
+ # Sets the global setting of whether to raise errors.
12
+ def self.raise_errors=(raise_errors)
13
+ @@raise_errors = raise_errors
14
+ end
15
+
16
+ # Returns the global setting of whether to raise errors.
17
+ def self.raise_errors?
18
+ @@raise_errors
19
+ end
20
+
21
+ # Sets a specific handler for errors to raise.
22
+ def self.error_handler(&blk)
23
+ @@error_handler = blk
24
+ end
25
+
26
+ # Expects a Net::HTTPResponse and handles errors.
27
+ def initialize(http)
28
+ @http = http
29
+
30
+ handle_soap_fault
31
+ handle_http_error
32
+ end
33
+
34
+ # Returns whether there was a SOAP fault.
35
+ def soap_fault?
36
+ @soap_fault ? true : false
37
+ end
38
+
39
+ # Returns the SOAP fault message.
40
+ attr_reader :soap_fault
41
+
42
+ # Returns whether there was an HTTP error.
43
+ def http_error?
44
+ @http_error ? true : false
45
+ end
46
+
47
+ # Returns the HTTP error message.
48
+ attr_reader :http_error
49
+
50
+ # Returns the SOAP response body as a Hash.
51
+ def to_hash
52
+ @body ||= (Crack::XML.parse(@http.body) rescue {}).find_soap_body
53
+ end
54
+
55
+ # Returns the SOAP response XML.
56
+ def to_xml
57
+ @http.body
58
+ end
59
+
60
+ # Returns the HTTP response object.
61
+ attr_reader :http
62
+
63
+ alias :to_s :to_xml
64
+
65
+ private
66
+
67
+ # Handles SOAP faults. Raises a Savon::SOAPFault unless the default
68
+ # behavior of raising errors was turned off.
69
+ def handle_soap_fault
70
+ if soap_fault_message
71
+ @soap_fault = soap_fault_message
72
+ raise Savon::SOAPFault, @soap_fault if self.class.raise_errors?
73
+ end
74
+ end
75
+
76
+ # Returns a SOAP fault message in case a SOAP fault was found.
77
+ def soap_fault_message
78
+ @soap_fault_message ||= soap_fault_message_by_version to_hash[:fault]
79
+ end
80
+
81
+ # Expects a Hash that might contain information about a SOAP fault.
82
+ # Returns the SOAP fault message in case one was found.
83
+ def soap_fault_message_by_version(soap_fault)
84
+ return unless soap_fault
85
+
86
+ if @@error_handler
87
+ @@error_handler.call(soap_fault)
88
+ else
89
+ if soap_fault.keys.include? :faultcode
90
+ "(#{soap_fault[:faultcode]}) #{soap_fault[:faultstring]}"
91
+ elsif soap_fault.keys.include? :code
92
+ "(#{soap_fault[:code][:value]}) #{soap_fault[:reason][:text]}"
93
+ end
94
+ end
95
+ end
96
+
97
+ # Handles HTTP errors. Raises a Savon::HTTPError unless the default
98
+ # behavior of raising errors was turned off.
99
+ def handle_http_error
100
+ if @http.code.to_i >= 300
101
+ @http_error = "#{@http.message} (#{@http.code})"
102
+ @http_error << ": #{@http.body}" unless @http.body.empty?
103
+ raise Savon::HTTPError, http_error if self.class.raise_errors?
104
+ end
105
+ end
106
+
107
+ end
108
+ end
data/lib/savon/soap.rb ADDED
@@ -0,0 +1,176 @@
1
+ module Savon
2
+
3
+ # == Savon::SOAP
4
+ #
5
+ # Represents the SOAP parameters and envelope.
6
+ class SOAP
7
+
8
+ # SOAP namespaces by SOAP version.
9
+ SOAPNamespace = {
10
+ 1 => "http://schemas.xmlsoap.org/soap/envelope/",
11
+ 2 => "http://www.w3.org/2003/05/soap-envelope"
12
+ }
13
+
14
+ # Content-Types by SOAP version.
15
+ ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
16
+
17
+ # The global SOAP version.
18
+ @@version = 1
19
+
20
+ # Returns the global SOAP version.
21
+ def self.version
22
+ @@version
23
+ end
24
+
25
+ # Sets the global SOAP version.
26
+ def self.version=(version)
27
+ @@version = version if Savon::SOAPVersions.include? version
28
+ end
29
+
30
+ # Sets the global SOAP header. Expected to be a Hash that can be translated
31
+ # to XML via Hash.to_soap_xml or any other Object responding to to_s.
32
+ def self.header=(header)
33
+ @@header = header
34
+ end
35
+
36
+ # Returns the global SOAP header. Defaults to an empty Hash.
37
+ def self.header
38
+ @@header ||= {}
39
+ end
40
+
41
+ # Sets the global namespaces. Expected to be a Hash containing the
42
+ # namespaces (keys) and the corresponding URI's (values).
43
+ def self.namespaces=(namespaces)
44
+ @@namespaces = namespaces if namespaces.kind_of? Hash
45
+ end
46
+
47
+ # Returns the global namespaces. A Hash containing the namespaces (keys)
48
+ # and the corresponding URI's (values).
49
+ def self.namespaces
50
+ @@namespaces ||= {}
51
+ end
52
+
53
+ # Initialzes the SOAP object.
54
+ def initialize
55
+ @builder = Builder::XmlMarkup.new
56
+ end
57
+
58
+ # Sets the WSSE options.
59
+ attr_writer :wsse
60
+
61
+ # Sets the SOAP action.
62
+ attr_writer :action
63
+
64
+ # Returns the SOAP action.
65
+ def action
66
+ @action ||= ""
67
+ end
68
+
69
+ # Sets the SOAP input.
70
+ attr_writer :input
71
+
72
+ # Returns the SOAP input.
73
+ def input
74
+ @input ||= ""
75
+ end
76
+
77
+ # Accessor for the SOAP endpoint.
78
+ attr_accessor :endpoint
79
+
80
+ # Sets the SOAP header. Expected to be a Hash that can be translated
81
+ # to XML via Hash.to_soap_xml or any other Object responding to to_s.
82
+ attr_writer :header
83
+
84
+ # Returns the SOAP header. Defaults to an empty Hash.
85
+ def header
86
+ @header ||= {}
87
+ end
88
+
89
+ # Sets the SOAP body. Expected to be a Hash that can be translated to
90
+ # XML via Hash.to_soap_xml or any other Object responding to to_s.
91
+ attr_writer :body
92
+
93
+ # Sets the namespaces. Expected to be a Hash containing the namespaces
94
+ # (keys) and the corresponding URI's (values).
95
+ attr_writer :namespaces
96
+
97
+ # Returns the namespaces. A Hash containing the namespaces (keys)
98
+ # and the corresponding URI's (values).
99
+ def namespaces
100
+ @namespaces ||= { "xmlns:env" => SOAPNamespace[version] }
101
+ end
102
+
103
+ # Convenience method for setting the "xmlns:wsdl" namespace.
104
+ def namespace=(namespace)
105
+ namespaces["xmlns:wsdl"] = namespace
106
+ end
107
+
108
+ # Sets the SOAP version.
109
+ def version=(version)
110
+ @version = version if Savon::SOAPVersions.include? version
111
+ end
112
+
113
+ # Returns the SOAP version. Defaults to the global default.
114
+ def version
115
+ @version ||= self.class.version
116
+ end
117
+
118
+ # Returns the SOAP envelope XML.
119
+ def to_xml
120
+ unless @xml_body
121
+ @xml_body = @builder.env :Envelope, all_namespaces do |xml|
122
+ xml_header xml
123
+ xml_body xml
124
+ end
125
+ end
126
+ @xml_body
127
+ end
128
+
129
+ private
130
+
131
+ # Adds a SOAP XML header to a given +xml+ Object.
132
+ def xml_header(xml)
133
+ xml.env(:Header) do
134
+ xml << all_header + wsse_header
135
+ end
136
+ end
137
+
138
+ # Returns a String containing the global and per request header.
139
+ def all_header
140
+ if self.class.header.kind_of?(Hash) && header.kind_of?(Hash)
141
+ self.class.header.merge(header).to_soap_xml
142
+ else
143
+ self.class.header.to_s + header.to_s
144
+ end
145
+ end
146
+
147
+ # Adds a SOAP XML body to a given +xml+ Object.
148
+ def xml_body(xml)
149
+ xml.env(:Body) do
150
+ xml.tag!(:wsdl, *input_array) do
151
+ xml << (@body.to_soap_xml rescue @body.to_s)
152
+ end
153
+ end
154
+ end
155
+
156
+ # Returns a Hash containing the global and per request namespaces.
157
+ def all_namespaces
158
+ self.class.namespaces.merge namespaces
159
+ end
160
+
161
+ # Returns an Array of SOAP input names to append to the :wsdl namespace.
162
+ # Defaults to use the name of the SOAP action and may be an empty Array
163
+ # in case the specified SOAP input seems invalid.
164
+ def input_array
165
+ return [input.to_sym] unless input.blank?
166
+ return [action.to_sym] unless action.blank?
167
+ []
168
+ end
169
+
170
+ # Returns the WSSE header or an empty String in case WSSE was not set.
171
+ def wsse_header
172
+ @wsse.respond_to?(:header) ? @wsse.header : ""
173
+ end
174
+
175
+ end
176
+ end
data/lib/savon/wsdl.rb ADDED
@@ -0,0 +1,122 @@
1
+ module Savon
2
+
3
+ # Savon::WSDL
4
+ #
5
+ # Represents the WSDL document.
6
+ class WSDL
7
+
8
+ # Initializer, expects a Savon::Request.
9
+ def initialize(request)
10
+ @request = request
11
+ end
12
+
13
+ # Sets whether to use the WSDL.
14
+ attr_writer :enabled
15
+
16
+ # Returns whether to use the WSDL. Defaults to +true+.
17
+ def enabled?
18
+ @enabled.nil? ? true : @enabled
19
+ end
20
+
21
+ # Returns the namespace URI of the WSDL.
22
+ def namespace_uri
23
+ @namespace_uri ||= stream.namespace_uri
24
+ end
25
+
26
+ # Returns an Array of available SOAP actions.
27
+ def soap_actions
28
+ @soap_actions ||= stream.operations.keys
29
+ end
30
+
31
+ # Returns a Hash of SOAP operations including their corresponding
32
+ # SOAP actions and inputs.
33
+ def operations
34
+ @operations ||= stream.operations
35
+ end
36
+
37
+ # Returns the SOAP endpoint.
38
+ def soap_endpoint
39
+ @soap_endpoint ||= stream.soap_endpoint
40
+ end
41
+
42
+ # Returns +true+ for available methods and SOAP actions.
43
+ def respond_to?(method)
44
+ return true if soap_actions.include? method
45
+ super
46
+ end
47
+
48
+ # Returns the raw WSDL document.
49
+ def to_s
50
+ @document ||= @request.wsdl.body
51
+ end
52
+
53
+ private
54
+
55
+ # Returns the Savon::WSDLStream.
56
+ def stream
57
+ unless @stream
58
+ @stream = WSDLStream.new
59
+ REXML::Document.parse_stream to_s, @stream
60
+ end
61
+ @stream
62
+ end
63
+
64
+ end
65
+
66
+ # Savon::WSDLStream
67
+ #
68
+ # Stream listener for parsing the WSDL document.
69
+ class WSDLStream
70
+
71
+ # The main sections of a WSDL document.
72
+ Sections = %w(definitions types message portType binding service)
73
+
74
+ def initialize
75
+ @depth, @operations = 0, {}
76
+ end
77
+
78
+ # Returns the namespace URI.
79
+ attr_reader :namespace_uri
80
+
81
+ # Returns the SOAP operations.
82
+ attr_reader :operations
83
+
84
+ # Returns the SOAP endpoint.
85
+ attr_reader :soap_endpoint
86
+
87
+ # Hook method called when the stream parser encounters a starting tag.
88
+ def tag_start(tag, attrs)
89
+ @depth += 1
90
+ tag = tag.strip_namespace
91
+
92
+ @section = tag.to_sym if @depth <= 2 && Sections.include?(tag)
93
+ @namespace_uri ||= attrs["targetNamespace"] if @section == :definitions
94
+ @soap_endpoint ||= URI(attrs["location"]) if @section == :service && tag == "address"
95
+
96
+ operation_from tag, attrs if @section == :binding && tag == "operation"
97
+ end
98
+
99
+ # Hook method called when the stream parser encounters a closing tag.
100
+ def tag_end(tag)
101
+ @depth -= 1
102
+ end
103
+
104
+ # Stores available operations from a given tag +name+ and +attrs+.
105
+ def operation_from(tag, attrs)
106
+ @input = attrs["name"] if attrs["name"]
107
+
108
+ if attrs["soapAction"]
109
+ @action = !attrs["soapAction"].blank? ? attrs["soapAction"] : @input
110
+ @input = @action.split("/").last if !@input || @input.empty?
111
+
112
+ @operations[@input.snakecase.to_sym] = { :action => @action, :input => @input }
113
+ @input, @action = nil, nil
114
+ end
115
+ end
116
+
117
+ # Catches calls to unimplemented hook methods.
118
+ def method_missing(method, *args)
119
+ end
120
+
121
+ end
122
+ end
data/lib/savon/wsse.rb ADDED
@@ -0,0 +1,136 @@
1
+ module Savon
2
+
3
+ # Savon::WSSE
4
+ #
5
+ # Represents parameters for WSSE authentication.
6
+ class WSSE
7
+
8
+ # Base address for WSSE docs.
9
+ BaseAddress = "http://docs.oasis-open.org/wss/2004/01"
10
+
11
+ # Namespace for WS Security Secext.
12
+ WSENamespace = BaseAddress + "/oasis-200401-wss-wssecurity-secext-1.0.xsd"
13
+
14
+ # Namespace for WS Security Utility.
15
+ WSUNamespace = BaseAddress + "/oasis-200401-wss-wssecurity-utility-1.0.xsd"
16
+
17
+ # URI for "wsse:Password/@Type" #PasswordText.
18
+ PasswordTextURI = BaseAddress + "/oasis-200401-wss-username-token-profile-1.0#PasswordText"
19
+
20
+ # URI for "wsse:Password/@Type" #PasswordDigest.
21
+ PasswordDigestURI = BaseAddress + "/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
22
+
23
+ # Global WSSE username.
24
+ @@username = nil
25
+
26
+ # Returns the global WSSE username.
27
+ def self.username
28
+ @@username
29
+ end
30
+
31
+ # Sets the global WSSE username.
32
+ def self.username=(username)
33
+ @@username = username.to_s if username.respond_to? :to_s
34
+ @@username = nil if username.nil?
35
+ end
36
+
37
+ # Global WSSE password.
38
+ @@password = nil
39
+
40
+ # Returns the global WSSE password.
41
+ def self.password
42
+ @@password
43
+ end
44
+
45
+ # Sets the global WSSE password.
46
+ def self.password=(password)
47
+ @@password = password.to_s if password.respond_to? :to_s
48
+ @@password = nil if password.nil?
49
+ end
50
+
51
+ # Global setting of whether to use WSSE digest.
52
+ @@digest = false
53
+
54
+ # Returns the global setting of whether to use WSSE digest.
55
+ def self.digest?
56
+ @@digest
57
+ end
58
+
59
+ # Global setting of whether to use WSSE digest.
60
+ def self.digest=(digest)
61
+ @@digest = digest
62
+ end
63
+
64
+ # Sets the WSSE username per request.
65
+ def username=(username)
66
+ @username = username.to_s if username.respond_to? :to_s
67
+ @username = nil if username.nil?
68
+ end
69
+
70
+ # Returns the WSSE username. Defaults to the global setting.
71
+ def username
72
+ @username || self.class.username
73
+ end
74
+
75
+ # Sets the WSSE password per request.
76
+ def password=(password)
77
+ @password = password.to_s if password.respond_to? :to_s
78
+ @password = nil if password.nil?
79
+ end
80
+
81
+ # Returns the WSSE password. Defaults to the global setting.
82
+ def password
83
+ @password || self.class.password
84
+ end
85
+
86
+ # Sets whether to use WSSE digest per request.
87
+ attr_writer :digest
88
+
89
+ # Returns whether to use WSSE digest. Defaults to the global setting.
90
+ def digest?
91
+ @digest || self.class.digest?
92
+ end
93
+
94
+ # Returns the XML for a WSSE header or an empty String unless both
95
+ # username and password were specified.
96
+ def header
97
+ return "" unless username && password
98
+
99
+ builder = Builder::XmlMarkup.new
100
+ builder.wsse :Security, "xmlns:wsse" => WSENamespace do |xml|
101
+ xml.wsse :UsernameToken, "xmlns:wsu" => WSUNamespace do
102
+ xml.wsse :Username, username
103
+ xml.wsse :Nonce, nonce
104
+ xml.wsu :Created, timestamp
105
+ xml.wsse :Password, password_node, :Type => password_type
106
+ end
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ # Returns the WSSE password. Encrypts the password for digest authentication.
113
+ def password_node
114
+ return password unless digest?
115
+
116
+ token = nonce + timestamp + password
117
+ Base64.encode64(Digest::SHA1.hexdigest(token)).chomp!
118
+ end
119
+
120
+ # Returns the URI for the "wsse:Password/@Type" attribute.
121
+ def password_type
122
+ digest? ? PasswordDigestURI : PasswordTextURI
123
+ end
124
+
125
+ # Returns a WSSE nonce.
126
+ def nonce
127
+ @nonce ||= Digest::SHA1.hexdigest String.random + timestamp
128
+ end
129
+
130
+ # Returns a WSSE timestamp.
131
+ def timestamp
132
+ @timestamp ||= Time.now.strftime Savon::SOAPDateTimeFormat
133
+ end
134
+
135
+ end
136
+ end