wash_out 0.9.0 → 0.12.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +10 -5
  4. data/Appraisals +16 -9
  5. data/Gemfile +3 -1
  6. data/README.md +112 -8
  7. data/Rakefile +15 -5
  8. data/app/helpers/wash_out_helper.rb +62 -25
  9. data/app/views/{wash_with_soap → wash_out}/document/error.builder +1 -1
  10. data/app/views/{wash_with_soap → wash_out}/document/response.builder +8 -3
  11. data/app/views/{wash_with_soap → wash_out}/document/wsdl.builder +16 -16
  12. data/app/views/{wash_with_soap → wash_out}/rpc/error.builder +1 -1
  13. data/app/views/{wash_with_soap → wash_out}/rpc/response.builder +9 -4
  14. data/app/views/{wash_with_soap → wash_out}/rpc/wsdl.builder +17 -17
  15. data/gemfiles/rails_3.2.13.gemfile +21 -0
  16. data/gemfiles/rails_4.0.0.gemfile +21 -0
  17. data/gemfiles/rails_4.1.0.gemfile +21 -0
  18. data/gemfiles/rails_4.2.0.gemfile +21 -0
  19. data/gemfiles/rails_5.0.0.beta2.gemfile +19 -0
  20. data/gemfiles/rails_5.0.0.gemfile +20 -0
  21. data/gemfiles/rails_5.1.1.gemfile +20 -0
  22. data/lib/wash_out/dispatcher.rb +126 -48
  23. data/lib/wash_out/engine.rb +1 -2
  24. data/lib/wash_out/model.rb +1 -1
  25. data/lib/wash_out/param.rb +14 -1
  26. data/lib/wash_out/router.rb +61 -19
  27. data/lib/wash_out/soap.rb +15 -3
  28. data/lib/wash_out/soap_config.rb +2 -0
  29. data/lib/wash_out/version.rb +1 -1
  30. data/lib/wash_out/wsse.rb +49 -23
  31. data/lib/wash_out.rb +35 -6
  32. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  33. data/spec/dummy/config/environments/test.rb +1 -0
  34. data/spec/fixtures/nested_refs_to_arrays.xml +19 -0
  35. data/spec/fixtures/ref_to_one_array.xml +11 -0
  36. data/spec/fixtures/refs_to_arrays.xml +16 -0
  37. data/spec/lib/wash_out/dispatcher_spec.rb +135 -13
  38. data/spec/lib/wash_out/middleware_spec.rb +8 -8
  39. data/spec/lib/wash_out/param_spec.rb +43 -11
  40. data/spec/lib/wash_out/router_spec.rb +50 -0
  41. data/spec/lib/wash_out/type_spec.rb +9 -9
  42. data/spec/lib/wash_out_spec.rb +440 -88
  43. data/spec/spec_helper.rb +26 -4
  44. metadata +27 -16
@@ -0,0 +1,19 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "wasabi"
6
+ gem "savon", ">= 2.0.0"
7
+ gem "httpi"
8
+ gem "rspec-rails"
9
+ gem "guard"
10
+ gem "guard-rspec"
11
+ gem "rb-fsevent"
12
+ gem "appraisal"
13
+ gem "tzinfo"
14
+ gem "pry"
15
+ gem "simplecov"
16
+ gem "simplecov-summary"
17
+ gem "rails", "5.0.0.beta2"
18
+
19
+ gemspec :path => "../"
@@ -0,0 +1,20 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "wasabi"
6
+ gem "savon", ">= 2.0.0"
7
+ gem "httpi"
8
+ gem "rspec-rails"
9
+ gem "guard"
10
+ gem "guard-rspec"
11
+ gem "rb-fsevent"
12
+ gem "appraisal"
13
+ gem "tzinfo"
14
+ gem "pry"
15
+ gem "simplecov"
16
+ gem "simplecov-summary"
17
+ gem "minitest", "<5.10.0"
18
+ gem "rails", "5.0.0"
19
+
20
+ gemspec path: "../"
@@ -0,0 +1,20 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "wasabi"
6
+ gem "savon", ">= 2.0.0"
7
+ gem "httpi"
8
+ gem "rspec-rails"
9
+ gem "guard"
10
+ gem "guard-rspec"
11
+ gem "rb-fsevent"
12
+ gem "appraisal"
13
+ gem "tzinfo"
14
+ gem "pry"
15
+ gem "simplecov"
16
+ gem "simplecov-summary"
17
+ gem "rails", "5.1.1"
18
+ gem "railties", "5.1.1"
19
+
20
+ gemspec path: "../"
@@ -5,13 +5,19 @@ module WashOut
5
5
  module Dispatcher
6
6
  # A SOAPError exception can be raised to return a correct SOAP error
7
7
  # response.
8
- class SOAPError < Exception; end
8
+ class SOAPError < Exception
9
+ attr_accessor :code
10
+ def initialize(message, code=nil)
11
+ super(message)
12
+ @code = code
13
+ end
14
+ end
15
+
9
16
  class ProgrammerError < Exception; end
10
17
 
11
18
  def _authenticate_wsse
12
-
13
19
  begin
14
- xml_security = env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
20
+ xml_security = request.env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
15
21
  xml_security = xml_security.values_at(:header, :Header).compact.first
16
22
  xml_security = xml_security.values_at(:security, :Security).compact.first
17
23
  username_token = xml_security.values_at(:username_token, :UsernameToken).compact.first
@@ -25,32 +31,31 @@ module WashOut
25
31
  end
26
32
 
27
33
  def _map_soap_parameters
34
+ self.params = _load_params action_spec[:in],
35
+ _strip_empty_nodes(action_spec[:in], xml_data)
36
+ end
28
37
 
29
- soap_action = request.env['wash_out.soap_action']
30
- action_spec = self.class.soap_actions[soap_action]
38
+ def _map_soap_headers
39
+ @_soap_headers = xml_header_data
40
+ end
31
41
 
32
- xml_data = env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
33
- xml_data = xml_data.values_at(:body, :Body).compact.first
34
- xml_data = xml_data.values_at(soap_action.underscore.to_sym,
35
- soap_action.to_sym).compact.first || {}
42
+ def _strip_empty_nodes(params, hash)
43
+ hash.keys.each do |key|
44
+ param = params.detect { |a| a.raw_name.to_s == key.to_s }
45
+ next if !(param && hash[key].is_a?(Hash))
36
46
 
37
- strip_empty_nodes = lambda{|hash|
38
- hash.keys.each do |key|
39
- if hash[key].is_a? Hash
40
- value = hash[key].delete_if{|k, v| key.to_s[0] == '@'}
47
+ value = hash[key].delete_if do |k, _|
48
+ k.to_s[0] == '@' && !param.map.detect { |a| a.raw_name.to_s == k.to_s }
49
+ end
41
50
 
42
- if value.length > 0
43
- hash[key] = strip_empty_nodes.call(value)
44
- else
45
- hash[key] = nil
46
- end
47
- end
51
+ if value.length > 0
52
+ hash[key] = _strip_empty_nodes param.map, value
53
+ else
54
+ hash[key] = nil
48
55
  end
56
+ end
49
57
 
50
- hash
51
- }
52
- xml_data = strip_empty_nodes.call(xml_data)
53
- @_params = _load_params(action_spec[:in], xml_data)
58
+ hash
54
59
  end
55
60
 
56
61
  # Creates the final parameter hash based on the request spec and xml_data from the request
@@ -67,12 +72,11 @@ module WashOut
67
72
 
68
73
  # This action generates the WSDL for defined SOAP methods.
69
74
  def _generate_wsdl
70
-
71
75
  @map = self.class.soap_actions
72
76
  @namespace = soap_config.namespace
73
- @name = controller_path.gsub('/', '_')
77
+ @name = controller_path
74
78
 
75
- render :template => "wash_with_soap/#{soap_config.wsdl_style}/wsdl", :layout => false,
79
+ render :template => "wash_out/#{soap_config.wsdl_style}/wsdl", :layout => false,
76
80
  :content_type => 'text/xml'
77
81
  end
78
82
 
@@ -127,9 +131,19 @@ module WashOut
127
131
  return result_spec
128
132
  }
129
133
 
130
- render :template => "wash_with_soap/#{soap_config.wsdl_style}/response",
134
+ header = options[:header]
135
+ if header.present?
136
+ header = { 'value' => header } unless header.is_a? Hash
137
+ header = HashWithIndifferentAccess.new(header)
138
+ end
139
+
140
+ render :template => "wash_out/#{soap_config.wsdl_style}/response",
131
141
  :layout => false,
132
- :locals => { :result => inject.call(result, @action_spec[:out]) },
142
+ :locals => {
143
+ :header => header.present? ? inject.call(header, @action_spec[:header_out])
144
+ : nil,
145
+ :result => inject.call(result, @action_spec[:out])
146
+ },
133
147
  :content_type => 'text/xml'
134
148
  end
135
149
 
@@ -138,49 +152,113 @@ module WashOut
138
152
  render_soap_error("Cannot find SOAP action mapping for #{request.env['wash_out.soap_action']}")
139
153
  end
140
154
 
141
- def _render_soap_exception(error)
142
- render_soap_error(error.message)
155
+ def _invalid_request
156
+ render_soap_error("Invalid SOAP request")
157
+ end
158
+
159
+ def _catch_soap_errors
160
+ yield
161
+ rescue SOAPError => error
162
+ render_soap_error(error.message, error.code)
143
163
  end
144
164
 
145
165
  # Render a SOAP error response.
146
166
  #
147
167
  # Rails do not support sequental rescue_from handling, that is, rescuing an
148
168
  # exception from a rescue_from handler. Hence this function is a public API.
149
- def render_soap_error(message)
150
- render :template => "wash_with_soap/#{soap_config.wsdl_style}/error", :status => 500,
169
+ def render_soap_error(message, code=nil)
170
+ render :template => "wash_out/#{soap_config.wsdl_style}/error", :status => 500,
151
171
  :layout => false,
152
- :locals => { :error_message => message },
172
+ :locals => { :error_message => message, :error_code => (code || 'Server') },
153
173
  :content_type => 'text/xml'
154
174
  end
155
175
 
176
+ def soap_request
177
+ OpenStruct.new({
178
+ params: @_params,
179
+ headers: @_soap_headers
180
+ })
181
+ end
182
+
156
183
  def self.included(controller)
157
- controller.send :rescue_from, SOAPError, :with => :_render_soap_exception
184
+ entity = if defined?(Rails::VERSION::MAJOR) && (Rails::VERSION::MAJOR >= 4)
185
+ 'action'
186
+ else
187
+ 'filter'
188
+ end
189
+
190
+ controller.send :"around_#{entity}", :_catch_soap_errors
158
191
  controller.send :helper, :wash_out
159
- controller.send :before_filter, :_authenticate_wsse, :except => [
160
- :_generate_wsdl, :_invalid_action ]
161
- controller.send :before_filter, :_map_soap_parameters, :except => [
162
- :_generate_wsdl, :_invalid_action ]
163
- controller.send :skip_before_filter, :verify_authenticity_token
192
+ controller.send :"before_#{entity}", :_authenticate_wsse, :if => :soap_action?
193
+ controller.send :"before_#{entity}", :_map_soap_parameters, :if => :soap_action?
194
+ controller.send :"before_#{entity}", :_map_soap_headers, :if => :soap_action?
195
+
196
+ if defined?(Rails::VERSION::MAJOR) && (Rails::VERSION::MAJOR >= 5)
197
+ controller.send :"skip_before_#{entity}", :verify_authenticity_token, :raise => false
198
+ else
199
+ controller.send :"skip_before_#{entity}", :verify_authenticity_token
200
+ end
164
201
  end
165
202
 
166
- def self.deep_select(hash, result=[], &blk)
167
- result += Hash[hash.select(&blk)].values
203
+ def self.deep_select(collection, result=[], &blk)
204
+ values = collection.respond_to?(:values) ? collection.values : collection
205
+ result += values.select(&blk)
168
206
 
169
- hash.each do |key, value|
170
- result = deep_select(value, result, &blk) if value.is_a? Hash
207
+ values.each do |value|
208
+ if value.is_a?(Hash) || value.is_a?(Array)
209
+ result = deep_select(value, result, &blk)
210
+ end
171
211
  end
172
212
 
173
213
  result
174
214
  end
175
215
 
176
- def self.deep_replace_href(hash, replace)
177
- return replace[hash[:@href]] if hash.has_key?(:@href)
216
+ def self.deep_replace_href(element, replace)
217
+ return element unless element.is_a?(Array) || element.is_a?(Hash)
178
218
 
179
- hash.keys.each do |key, value|
180
- hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
219
+ if element.is_a?(Array) # Traverse arrays
220
+ return element.map{|x| deep_replace_href(x, replace)}
181
221
  end
182
222
 
183
- hash
223
+ if element.has_key?(:@href) # Replace needle and traverse replacement
224
+ return deep_replace_href(replace[element[:@href]], replace)
225
+ end
226
+
227
+ element.each do |key, value| # Traverse hashes
228
+ element[key] = deep_replace_href(value, replace)
229
+ end
230
+
231
+ element
184
232
  end
233
+
234
+ private
235
+ def soap_action?
236
+ soap_action.present?
237
+ end
238
+
239
+ def action_spec
240
+ self.class.soap_actions[soap_action]
241
+ end
242
+
243
+ def request_input_tag
244
+ action_spec[:request_tag]
245
+ end
246
+
247
+ def soap_action
248
+ request.env['wash_out.soap_action']
249
+ end
250
+
251
+ def xml_data
252
+ envelope = request.env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
253
+ xml_data = envelope.values_at(:body, :Body).compact.first || {}
254
+ return xml_data if soap_config.wsdl_style == "document"
255
+ xml_data = xml_data.values_at(soap_action.underscore.to_sym, soap_action.to_sym, request_input_tag.to_sym).compact.first || {}
256
+ end
257
+
258
+ def xml_header_data
259
+ envelope = request.env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
260
+ header_data = envelope.values_at(:header, :Header).compact.first || {}
261
+ end
262
+
185
263
  end
186
264
  end
@@ -1,10 +1,9 @@
1
-
2
1
  module WashOut
3
2
  class Engine < ::Rails::Engine
4
3
  config.wash_out = ActiveSupport::OrderedOptions.new
5
4
  initializer "wash_out.configuration" do |app|
6
5
  if app.config.wash_out[:catch_xml_errors]
7
- app.config.middleware.insert_after 'ActionDispatch::ShowExceptions', WashOut::Middleware
6
+ app.config.middleware.insert_after(ActionDispatch::ShowExceptions, WashOut::Middleware)
8
7
  end
9
8
  end
10
9
 
@@ -22,7 +22,7 @@ module WashOut
22
22
  map
23
23
  end
24
24
 
25
- def wash_out_param_name
25
+ def wash_out_param_name(*args)
26
26
  return name.underscore
27
27
  end
28
28
  end
@@ -7,6 +7,7 @@ module WashOut
7
7
  attr_accessor :multiplied
8
8
  attr_accessor :value
9
9
  attr_accessor :source_class
10
+ attr_accessor :soap_config
10
11
 
11
12
  # Defines a WSDL parameter with name +name+ and type specifier +type+.
12
13
  # The type specifier format is described in #parse_def.
@@ -63,6 +64,7 @@ module WashOut
63
64
  operation = case type
64
65
  when 'string'; :to_s
65
66
  when 'integer'; :to_i
67
+ when 'long'; :to_i
66
68
  when 'double'; :to_f
67
69
  when 'boolean'; lambda{|dat| dat === "0" ? false : !!dat}
68
70
  when 'date'; :to_date
@@ -160,9 +162,19 @@ module WashOut
160
162
  def flat_copy
161
163
  copy = self.class.new(@soap_config, @name, @type.to_sym, @multiplied)
162
164
  copy.raw_name = raw_name
165
+ copy.source_class = source_class
163
166
  copy
164
167
  end
165
168
 
169
+ def attribute?
170
+ name[0] == "@"
171
+ end
172
+
173
+ def attr_name
174
+ raise 'Not attribute' unless attribute?
175
+ name[1..-1]
176
+ end
177
+
166
178
  private
167
179
 
168
180
  # Used to load an entire structure.
@@ -177,7 +189,8 @@ module WashOut
177
189
  # RUBY18 Enumerable#each_with_object is better, but 1.9 only.
178
190
  @map.map do |param|
179
191
  if data.has_key? param.raw_name
180
- struct[param.raw_name] = yield param, data, param.raw_name
192
+ param_name = param.attribute? ? param.attr_name : param.raw_name
193
+ struct[param_name] = yield param, data, param.raw_name
181
194
  end
182
195
  end
183
196
 
@@ -4,6 +4,40 @@ module WashOut
4
4
  # This class is a Rack middleware used to route SOAP requests to a proper
5
5
  # action of a given SOAP controller.
6
6
  class Router
7
+ def self.lookup_soap_routes(controller_name, routes, path=[], &block)
8
+ routes.each do |x|
9
+ defaults = x.defaults
10
+ defaults = defaults[:defaults] if defaults.include?(:defaults) # Rails 5
11
+ if defaults[:controller] == controller_name && defaults[:action] == 'soap'
12
+ yield path+[x]
13
+ end
14
+
15
+ app = x.app
16
+ app = app.app if app.respond_to?(:app)
17
+ if app.respond_to?(:routes) && app.routes.respond_to?(:routes)
18
+ lookup_soap_routes(controller_name, app.routes.routes, path+[x], &block)
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.url(request, controller_name)
24
+ lookup_soap_routes(controller_name, Rails.application.routes.routes) do |routes|
25
+
26
+ path = if routes.first.respond_to?(:optimized_path) # Rails 4
27
+ routes.map(&:optimized_path)
28
+ elsif routes.first.path.respond_to?(:build_formatter) # Rails 5
29
+ routes.map{|x| x.path.build_formatter.evaluate(nil)}
30
+ else
31
+ routes.map{|x| x.format({})} # Rails 3.2
32
+ end
33
+
34
+ if Rails.application.config.relative_url_root.present?
35
+ path.prepend Rails.application.config.relative_url_root
36
+ end
37
+ return request.protocol + request.host_with_port + path.flatten.join('')
38
+ end
39
+ end
40
+
7
41
  def initialize(controller_name)
8
42
  @controller_name = "#{controller_name.to_s}_controller".camelize
9
43
  end
@@ -15,13 +49,15 @@ module WashOut
15
49
  def parse_soap_action(env)
16
50
  return env['wash_out.soap_action'] if env['wash_out.soap_action']
17
51
 
18
- soap_action = env['HTTP_SOAPACTION'].to_s.gsub(/^"(.*)"$/, '\1')
19
-
52
+ soap_action = controller.soap_config.soap_action_routing ? env['HTTP_SOAPACTION'].to_s.gsub(/^"(.*)"$/, '\1')
53
+ : ''
20
54
  if soap_action.blank?
21
- soap_action = nori.parse(soap_body env)
22
- .values_at(:envelope, :Envelope).compact.first
23
- .values_at(:body, :Body).compact.first
24
- .keys.first.to_s
55
+ parsed_soap_body = nori(controller.soap_config.snakecase_input).parse(soap_body env)
56
+ return nil if parsed_soap_body.blank?
57
+
58
+ soap_action = parsed_soap_body.values_at(:envelope, :Envelope).try(:compact).try(:first)
59
+ soap_action = soap_action.values_at(:body, :Body).try(:compact).try(:first) if soap_action
60
+ soap_action = soap_action.keys.first.to_s if soap_action
25
61
  end
26
62
 
27
63
  # RUBY18 1.8 does not have force_encoding.
@@ -41,22 +77,24 @@ module WashOut
41
77
  :strip_namespaces => true,
42
78
  :advanced_typecasting => true,
43
79
  :convert_tags_to => (
44
- snakecase ? lambda { |tag| tag.snakecase.to_sym }
80
+ snakecase ? lambda { |tag| tag.snakecase.to_sym }
45
81
  : lambda { |tag| tag.to_sym }
46
82
  )
47
83
  )
48
84
  end
49
85
 
50
86
  def soap_body(env)
51
- env['rack.input'].respond_to?(:string) ? env['rack.input'].string
52
- : env['rack.input'].read
87
+ body = env['rack.input']
88
+ body.rewind if body.respond_to? :rewind
89
+ body.respond_to?(:string) ? body.string : body.read
90
+ ensure
91
+ body.rewind if body.respond_to? :rewind
53
92
  end
54
93
 
55
94
  def parse_soap_parameters(env)
56
95
  return env['wash_out.soap_data'] if env['wash_out.soap_data']
57
-
58
96
  env['wash_out.soap_data'] = nori(controller.soap_config.snakecase_input).parse(soap_body env)
59
- references = WashOut::Dispatcher.deep_select(env['wash_out.soap_data']){|k,v| v.is_a?(Hash) && v.has_key?(:@id)}
97
+ references = WashOut::Dispatcher.deep_select(env['wash_out.soap_data']){|v| v.is_a?(Hash) && v.has_key?(:@id)}
60
98
 
61
99
  unless references.blank?
62
100
  replaces = {}; references.each{|r| replaces['#'+r[:@id]] = r}
@@ -69,17 +107,21 @@ module WashOut
69
107
  def call(env)
70
108
  @controller = @controller_name.constantize
71
109
 
72
- soap_action = parse_soap_action(env)
73
- soap_parameters = parse_soap_parameters(env)
110
+ soap_action = parse_soap_action(env)
74
111
 
75
- action_spec = controller.soap_actions[soap_action]
76
-
77
- if action_spec
78
- action = action_spec[:to]
112
+ action = if soap_action.blank?
113
+ '_invalid_request'
79
114
  else
80
- action = '_invalid_action'
115
+ soap_parameters = parse_soap_parameters(env)
116
+ action_spec = controller.soap_actions[soap_action]
117
+
118
+ if action_spec
119
+ action_spec[:to]
120
+ else
121
+ '_invalid_action'
122
+ end
81
123
  end
82
-
124
+ env["action_dispatch.request.content_type"] = Mime[:soap]
83
125
  controller.action(action).call(env)
84
126
  end
85
127
  end
data/lib/wash_out/soap.rb CHANGED
@@ -13,7 +13,15 @@ module WashOut
13
13
  #
14
14
  # An optional option :to can be passed to allow for names of SOAP actions
15
15
  # which are not valid Ruby function names.
16
+ # There is also an optional :header_return option to specify the format of the
17
+ # SOAP response's header tag (<env:Header></env:Header>). If unspecified, there will
18
+ # be no header tag in the response.
16
19
  def soap_action(action, options={})
20
+ if options[:as].present?
21
+ options[:to] ||= action
22
+ action = options[:as]
23
+ end
24
+
17
25
  if action.is_a?(Symbol)
18
26
  if soap_config.camelize_wsdl.to_s == 'lower'
19
27
  options[:to] ||= action.to_s
@@ -26,20 +34,24 @@ module WashOut
26
34
  end
27
35
 
28
36
  default_response_tag = soap_config.camelize_wsdl ? 'Response' : '_response'
29
- default_response_tag = "tns:#{action}#{default_response_tag}"
37
+ default_response_tag = action+default_response_tag
38
+
30
39
 
31
- self.soap_actions[action] = {
40
+ self.soap_actions[action] = options.merge(
32
41
  :in => WashOut::Param.parse_def(soap_config, options[:args]),
42
+ :request_tag => options[:as] || action,
33
43
  :out => WashOut::Param.parse_def(soap_config, options[:return]),
44
+ :header_out => options[:header_return].present? ? WashOut::Param.parse_def(soap_config, options[:header_return]) : nil,
34
45
  :to => options[:to] || action,
35
46
  :response_tag => options[:response_tag] || default_response_tag
36
- }
47
+ )
37
48
  end
38
49
  end
39
50
 
40
51
  included do
41
52
  include WashOut::Configurable
42
53
  include WashOut::Dispatcher
54
+ include WashOut::WsseParams
43
55
  self.soap_actions = {}
44
56
  end
45
57
  end
@@ -13,6 +13,8 @@ module WashOut
13
13
  catch_xml_errors: false,
14
14
  wsse_username: nil,
15
15
  wsse_password: nil,
16
+ wsse_auth_callback: nil,
17
+ soap_action_routing: true,
16
18
  }
17
19
 
18
20
  attr_reader :config
@@ -1,3 +1,3 @@
1
1
  module WashOut
2
- VERSION = "0.9.0"
2
+ VERSION = "0.12.0"
3
3
  end
data/lib/wash_out/wsse.rb CHANGED
@@ -1,4 +1,13 @@
1
1
  module WashOut
2
+
3
+ module WsseParams
4
+ def wsse_username
5
+ if request.env['WSSE_TOKEN']
6
+ request.env['WSSE_TOKEN'].values_at(:username, :Username).compact.first
7
+ end
8
+ end
9
+ end
10
+
2
11
  class Wsse
3
12
  attr_reader :soap_config
4
13
  def self.authenticate(soap_config, token)
@@ -18,7 +27,15 @@ module WashOut
18
27
  end
19
28
 
20
29
  def required?
21
- !soap_config.wsse_username.blank?
30
+ !soap_config.wsse_username.blank? || auth_callback?
31
+ end
32
+
33
+ def auth_callback?
34
+ return !!soap_config.wsse_auth_callback && soap_config.wsse_auth_callback.respond_to?(:call) && soap_config.wsse_auth_callback.arity == 4
35
+ end
36
+
37
+ def perform_auth_callback(user, password, nonce, timestamp)
38
+ soap_config.wsse_auth_callback.call(user, password, nonce, timestamp)
22
39
  end
23
40
 
24
41
  def expected_user
@@ -29,12 +46,35 @@ module WashOut
29
46
  soap_config.wsse_password
30
47
  end
31
48
 
32
- def matches_expected_digest?(password)
33
- nonce = @username_token.values_at(:nonce, :Nonce).compact.first
49
+ def eligible?
50
+ return true unless required?
51
+
52
+ user = @username_token.values_at(:username, :Username).compact.first
53
+ password = @username_token.values_at(:password, :Password).compact.first
54
+
55
+ nonce = @username_token.values_at(:nonce, :Nonce).compact.first
34
56
  timestamp = @username_token.values_at(:created, :Created).compact.first
35
57
 
58
+ if (expected_user == user && self.class.matches_expected_digest?(expected_password, password, nonce, timestamp))
59
+ return true
60
+ end
61
+
62
+ if auth_callback?
63
+ return perform_auth_callback(user, password, nonce, timestamp)
64
+ end
65
+
66
+ if (expected_user == user && expected_password == password)
67
+ return true
68
+ end
69
+
70
+ return false
71
+ end
72
+
73
+ def self.matches_expected_digest?(expected_password, password, nonce, timestamp)
36
74
  return false if nonce.nil? || timestamp.nil?
37
75
 
76
+ timestamp = timestamp.to_datetime
77
+
38
78
  # Token should not be accepted if timestamp is older than 5 minutes ago
39
79
  # http://www.oasis-open.org/committees/download.php/16782/wss-v1.1-spec-os-UsernameTokenProfile.pdf
40
80
  offset_in_minutes = ((DateTime.now - timestamp)* 24 * 60).to_i
@@ -45,11 +85,15 @@ module WashOut
45
85
  flavors = Array.new
46
86
 
47
87
  # Ruby / Savon
48
- token = nonce + timestamp.to_s + expected_password
88
+ token = nonce + timestamp.strftime("%Y-%m-%dT%H:%M:%SZ") + expected_password
49
89
  flavors << Base64.encode64(Digest::SHA1.hexdigest(token)).chomp!
50
90
 
51
91
  # Java
52
- token = Base64.decode64(nonce) + timestamp.to_s + expected_password
92
+ token = Base64.decode64(nonce) + timestamp.strftime("%Y-%m-%dT%H:%M:%SZ") + expected_password
93
+ flavors << Base64.encode64(Digest::SHA1.digest(token)).chomp!
94
+
95
+ # SoapUI
96
+ token = Base64.decode64(nonce) + timestamp.strftime("%Y-%m-%dT%H:%M:%S.%3NZ") + expected_password
53
97
  flavors << Base64.encode64(Digest::SHA1.digest(token)).chomp!
54
98
 
55
99
  flavors.each do |f|
@@ -58,23 +102,5 @@ module WashOut
58
102
 
59
103
  return false
60
104
  end
61
-
62
- def eligible?
63
- return true unless required?
64
-
65
- user = @username_token.values_at(:username, :Username).compact.first
66
- password = @username_token.values_at(:password, :Password).compact.first
67
-
68
- if (expected_user == user && expected_password == password)
69
- return true
70
- end
71
-
72
- if (expected_user == user && matches_expected_digest?(password))
73
- return true
74
- end
75
-
76
- return false
77
- end
78
-
79
105
  end
80
106
  end