savon-xaop 0.7.2.1

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