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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.travis.yml +10 -5
- data/Appraisals +16 -9
- data/Gemfile +3 -1
- data/README.md +112 -8
- data/Rakefile +15 -5
- data/app/helpers/wash_out_helper.rb +62 -25
- data/app/views/{wash_with_soap → wash_out}/document/error.builder +1 -1
- data/app/views/{wash_with_soap → wash_out}/document/response.builder +8 -3
- data/app/views/{wash_with_soap → wash_out}/document/wsdl.builder +16 -16
- data/app/views/{wash_with_soap → wash_out}/rpc/error.builder +1 -1
- data/app/views/{wash_with_soap → wash_out}/rpc/response.builder +9 -4
- data/app/views/{wash_with_soap → wash_out}/rpc/wsdl.builder +17 -17
- data/gemfiles/rails_3.2.13.gemfile +21 -0
- data/gemfiles/rails_4.0.0.gemfile +21 -0
- data/gemfiles/rails_4.1.0.gemfile +21 -0
- data/gemfiles/rails_4.2.0.gemfile +21 -0
- data/gemfiles/rails_5.0.0.beta2.gemfile +19 -0
- data/gemfiles/rails_5.0.0.gemfile +20 -0
- data/gemfiles/rails_5.1.1.gemfile +20 -0
- data/lib/wash_out/dispatcher.rb +126 -48
- data/lib/wash_out/engine.rb +1 -2
- data/lib/wash_out/model.rb +1 -1
- data/lib/wash_out/param.rb +14 -1
- data/lib/wash_out/router.rb +61 -19
- data/lib/wash_out/soap.rb +15 -3
- data/lib/wash_out/soap_config.rb +2 -0
- data/lib/wash_out/version.rb +1 -1
- data/lib/wash_out/wsse.rb +49 -23
- data/lib/wash_out.rb +35 -6
- data/spec/dummy/app/controllers/application_controller.rb +1 -1
- data/spec/dummy/config/environments/test.rb +1 -0
- data/spec/fixtures/nested_refs_to_arrays.xml +19 -0
- data/spec/fixtures/ref_to_one_array.xml +11 -0
- data/spec/fixtures/refs_to_arrays.xml +16 -0
- data/spec/lib/wash_out/dispatcher_spec.rb +135 -13
- data/spec/lib/wash_out/middleware_spec.rb +8 -8
- data/spec/lib/wash_out/param_spec.rb +43 -11
- data/spec/lib/wash_out/router_spec.rb +50 -0
- data/spec/lib/wash_out/type_spec.rb +9 -9
- data/spec/lib/wash_out_spec.rb +440 -88
- data/spec/spec_helper.rb +26 -4
- 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: "../"
|
data/lib/wash_out/dispatcher.rb
CHANGED
@@ -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
|
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
|
-
|
30
|
-
|
38
|
+
def _map_soap_headers
|
39
|
+
@_soap_headers = xml_header_data
|
40
|
+
end
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
77
|
+
@name = controller_path
|
74
78
|
|
75
|
-
render :template => "
|
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
|
-
|
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 => {
|
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
|
142
|
-
render_soap_error(
|
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 => "
|
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
|
-
|
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 :
|
160
|
-
|
161
|
-
controller.send :
|
162
|
-
|
163
|
-
|
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(
|
167
|
-
|
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
|
-
|
170
|
-
|
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(
|
177
|
-
return
|
216
|
+
def self.deep_replace_href(element, replace)
|
217
|
+
return element unless element.is_a?(Array) || element.is_a?(Hash)
|
178
218
|
|
179
|
-
|
180
|
-
|
219
|
+
if element.is_a?(Array) # Traverse arrays
|
220
|
+
return element.map{|x| deep_replace_href(x, replace)}
|
181
221
|
end
|
182
222
|
|
183
|
-
|
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
|
data/lib/wash_out/engine.rb
CHANGED
@@ -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
|
6
|
+
app.config.middleware.insert_after(ActionDispatch::ShowExceptions, WashOut::Middleware)
|
8
7
|
end
|
9
8
|
end
|
10
9
|
|
data/lib/wash_out/model.rb
CHANGED
data/lib/wash_out/param.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/wash_out/router.rb
CHANGED
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
52
|
-
|
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']){|
|
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
|
73
|
-
soap_parameters = parse_soap_parameters(env)
|
110
|
+
soap_action = parse_soap_action(env)
|
74
111
|
|
75
|
-
|
76
|
-
|
77
|
-
if action_spec
|
78
|
-
action = action_spec[:to]
|
112
|
+
action = if soap_action.blank?
|
113
|
+
'_invalid_request'
|
79
114
|
else
|
80
|
-
|
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 =
|
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
|
data/lib/wash_out/soap_config.rb
CHANGED
data/lib/wash_out/version.rb
CHANGED
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
|
33
|
-
|
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.
|
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.
|
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
|