wash_out 0.10.0.beta.1 → 0.10.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 +23 -3
- data/Appraisals +11 -5
- data/Gemfile +2 -2
- data/README.md +66 -2
- data/Rakefile +6 -7
- data/app/helpers/wash_out_helper.rb +49 -18
- data/app/views/{wash_with_soap → wash_out}/document/error.builder +0 -0
- data/app/views/{wash_with_soap → wash_out}/document/response.builder +1 -3
- data/app/views/{wash_with_soap → wash_out}/document/wsdl.builder +15 -15
- data/app/views/{wash_with_soap → wash_out}/rpc/error.builder +0 -0
- data/app/views/{wash_with_soap → wash_out}/rpc/response.builder +1 -3
- data/app/views/{wash_with_soap → wash_out}/rpc/wsdl.builder +16 -16
- data/gemfiles/rails_3.1.3.gemfile +20 -0
- data/gemfiles/rails_3.2.12.gemfile +20 -0
- data/gemfiles/rails_4.0.0.gemfile +19 -0
- data/gemfiles/rails_4.1.0.gemfile +19 -0
- data/gemfiles/rails_4.2.0.gemfile +19 -0
- data/lib/wash_out.rb +48 -16
- data/lib/wash_out/configurable.rb +41 -0
- data/lib/wash_out/dispatcher.rb +212 -0
- data/lib/wash_out/engine.rb +12 -0
- data/lib/wash_out/middleware.rb +41 -0
- data/lib/wash_out/model.rb +29 -0
- data/lib/wash_out/param.rb +43 -16
- data/lib/wash_out/router.rb +95 -0
- data/lib/wash_out/soap.rb +48 -0
- data/lib/wash_out/soap_config.rb +93 -0
- data/lib/wash_out/type.rb +20 -13
- data/lib/wash_out/version.rb +1 -1
- data/lib/wash_out/wsse.rb +29 -10
- data/spec/dummy/config/environments/test.rb +1 -0
- data/spec/lib/wash_out/dispatcher_spec.rb +28 -13
- data/spec/lib/wash_out/middleware_spec.rb +33 -0
- data/spec/lib/wash_out/param_spec.rb +47 -17
- data/spec/lib/wash_out/router_spec.rb +22 -0
- data/spec/lib/wash_out/type_spec.rb +9 -9
- data/spec/lib/wash_out_spec.rb +139 -87
- data/spec/spec_helper.rb +7 -1
- metadata +32 -25
- data/lib/wash_out/exceptions/programmer_error.rb +0 -10
- data/lib/wash_out/exceptions/soap_error.rb +0 -19
- data/lib/wash_out/middlewares/catcher.rb +0 -42
- data/lib/wash_out/middlewares/router.rb +0 -132
- data/lib/wash_out/rails/active_record.rb +0 -27
- data/lib/wash_out/rails/controller.rb +0 -201
- data/lib/wash_out/rails/engine.rb +0 -74
- data/spec/lib/wash_out/rack_spec.rb +0 -55
@@ -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", "3.2.12"
|
18
|
+
gem "test-unit"
|
19
|
+
|
20
|
+
gemspec :path => "../"
|
@@ -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", "4.0.0"
|
18
|
+
|
19
|
+
gemspec :path => "../"
|
@@ -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", "4.1.0"
|
18
|
+
|
19
|
+
gemspec :path => "../"
|
@@ -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", "4.2.0"
|
18
|
+
|
19
|
+
gemspec :path => "../"
|
data/lib/wash_out.rb
CHANGED
@@ -1,21 +1,53 @@
|
|
1
|
-
require 'wash_out/
|
2
|
-
require 'wash_out/
|
1
|
+
require 'wash_out/configurable'
|
2
|
+
require 'wash_out/soap_config'
|
3
|
+
require 'wash_out/soap'
|
4
|
+
require 'wash_out/engine'
|
5
|
+
require 'wash_out/param'
|
6
|
+
require 'wash_out/dispatcher'
|
7
|
+
require 'wash_out/soap'
|
8
|
+
require 'wash_out/router'
|
9
|
+
require 'wash_out/type'
|
10
|
+
require 'wash_out/model'
|
11
|
+
require 'wash_out/wsse'
|
12
|
+
require 'wash_out/middleware'
|
3
13
|
|
4
|
-
|
5
|
-
|
14
|
+
module WashOut
|
15
|
+
def self.root
|
16
|
+
File.expand_path '../..', __FILE__
|
17
|
+
end
|
18
|
+
end
|
6
19
|
|
7
|
-
|
8
|
-
|
9
|
-
|
20
|
+
module ActionDispatch::Routing
|
21
|
+
class Mapper
|
22
|
+
# Adds the routes for a SOAP endpoint at +controller+.
|
23
|
+
def wash_out(controller_name, options={})
|
24
|
+
options.each_with_index { |key, value| @scope[key] = value } if @scope
|
25
|
+
controller_class_name = [options[:module], controller_name].compact.join("/")
|
10
26
|
|
11
|
-
|
12
|
-
|
13
|
-
|
27
|
+
match "#{controller_name}/wsdl" => "#{controller_name}#_generate_wsdl", :via => :get, :format => false
|
28
|
+
match "#{controller_name}/action" => WashOut::Router.new(controller_class_name), :via => [:get, :post], :defaults => { :controller => controller_class_name, :action => '_action' }, :format => false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
14
32
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
33
|
+
Mime::Type.register "application/soap+xml", :soap
|
34
|
+
ActiveRecord::Base.send :extend, WashOut::Model if defined?(ActiveRecord)
|
35
|
+
|
36
|
+
ActionController::Renderers.add :soap do |what, options|
|
37
|
+
_render_soap(what, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
ActionController::Base.class_eval do
|
41
|
+
|
42
|
+
# Define a SOAP service. The function has no required +options+:
|
43
|
+
# but allow any of :parser, :namespace, :wsdl_style, :snakecase_input,
|
44
|
+
# :camelize_wsdl, :wsse_username, :wsse_password and :catch_xml_errors.
|
45
|
+
#
|
46
|
+
# Any of the the params provided allows for overriding the defaults
|
47
|
+
# (like supporting multiple namespaces instead of application wide such)
|
48
|
+
#
|
49
|
+
def self.soap_service(options={})
|
50
|
+
include WashOut::SOAP
|
51
|
+
self.soap_config = options
|
20
52
|
end
|
21
|
-
end
|
53
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module WashOut
|
2
|
+
module Configurable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
cattr_reader :soap_config
|
7
|
+
class_variable_set :@@soap_config, WashOut::SoapConfig.new({})
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def soap_config=(obj)
|
13
|
+
|
14
|
+
unless obj.is_a?(Hash)
|
15
|
+
raise "Value needs to be a Hash."
|
16
|
+
end
|
17
|
+
|
18
|
+
if class_variable_defined?(:@@soap_config)
|
19
|
+
class_variable_get(:@@soap_config).configure obj
|
20
|
+
else
|
21
|
+
class_variable_set :@@soap_config, WashOut::SoapConfig.new(obj)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def soap_config=(obj)
|
27
|
+
|
28
|
+
unless obj.is_a?(Hash)
|
29
|
+
raise "Value needs to be a Hash."
|
30
|
+
end
|
31
|
+
|
32
|
+
class_eval do
|
33
|
+
if class_variable_defined?(:@@soap_config)
|
34
|
+
class_variable_get(:@@soap_config).configure obj
|
35
|
+
else
|
36
|
+
class_variable_set :@@soap_config, WashOut::SoapConfig.new(obj)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module WashOut
|
2
|
+
# The WashOut::Dispatcher module should be included in a controller acting
|
3
|
+
# as a SOAP endpoint. It includes actions for generating WSDL and handling
|
4
|
+
# SOAP requests.
|
5
|
+
module Dispatcher
|
6
|
+
# A SOAPError exception can be raised to return a correct SOAP error
|
7
|
+
# response.
|
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
|
+
|
16
|
+
class ProgrammerError < Exception; end
|
17
|
+
|
18
|
+
def _authenticate_wsse
|
19
|
+
|
20
|
+
begin
|
21
|
+
xml_security = env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
|
22
|
+
xml_security = xml_security.values_at(:header, :Header).compact.first
|
23
|
+
xml_security = xml_security.values_at(:security, :Security).compact.first
|
24
|
+
username_token = xml_security.values_at(:username_token, :UsernameToken).compact.first
|
25
|
+
rescue
|
26
|
+
username_token = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
WashOut::Wsse.authenticate soap_config, username_token
|
30
|
+
|
31
|
+
request.env['WSSE_TOKEN'] = username_token.with_indifferent_access unless username_token.blank?
|
32
|
+
end
|
33
|
+
|
34
|
+
def _map_soap_parameters
|
35
|
+
@_params = _load_params action_spec[:in],
|
36
|
+
_strip_empty_nodes(action_spec[:in], xml_data)
|
37
|
+
end
|
38
|
+
|
39
|
+
def _strip_empty_nodes(params, hash)
|
40
|
+
hash.keys.each do |key|
|
41
|
+
param = params.detect { |a| a.raw_name.to_s == key.to_s }
|
42
|
+
next if !(param && hash[key].is_a?(Hash))
|
43
|
+
|
44
|
+
value = hash[key].delete_if do |k, _|
|
45
|
+
k.to_s[0] == '@' && !param.map.detect { |a| a.raw_name.to_s == k.to_s }
|
46
|
+
end
|
47
|
+
|
48
|
+
if value.length > 0
|
49
|
+
hash[key] = _strip_empty_nodes param.map, value
|
50
|
+
else
|
51
|
+
hash[key] = nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates the final parameter hash based on the request spec and xml_data from the request
|
59
|
+
def _load_params(spec, xml_data)
|
60
|
+
params = HashWithIndifferentAccess.new
|
61
|
+
spec.each do |param|
|
62
|
+
key = param.raw_name.to_sym
|
63
|
+
if xml_data.has_key? key
|
64
|
+
params[param.raw_name] = param.load(xml_data, key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
params
|
68
|
+
end
|
69
|
+
|
70
|
+
# This action generates the WSDL for defined SOAP methods.
|
71
|
+
def _generate_wsdl
|
72
|
+
|
73
|
+
@map = self.class.soap_actions
|
74
|
+
@namespace = soap_config.namespace
|
75
|
+
@name = controller_path.gsub('/', '_')
|
76
|
+
|
77
|
+
render :template => "wash_out/#{soap_config.wsdl_style}/wsdl", :layout => false,
|
78
|
+
:content_type => 'text/xml'
|
79
|
+
end
|
80
|
+
|
81
|
+
# Render a SOAP response.
|
82
|
+
def _render_soap(result, options)
|
83
|
+
@namespace = soap_config.namespace
|
84
|
+
@operation = soap_action = request.env['wash_out.soap_action']
|
85
|
+
@action_spec = self.class.soap_actions[soap_action]
|
86
|
+
|
87
|
+
result = { 'value' => result } unless result.is_a? Hash
|
88
|
+
result = HashWithIndifferentAccess.new(result)
|
89
|
+
|
90
|
+
inject = lambda {|data, map|
|
91
|
+
result_spec = []
|
92
|
+
return result_spec if data.nil?
|
93
|
+
|
94
|
+
map.each_with_index do |param, i|
|
95
|
+
result_spec[i] = param.flat_copy
|
96
|
+
|
97
|
+
unless data.is_a?(Hash)
|
98
|
+
raise ProgrammerError,
|
99
|
+
"SOAP response used #{data.inspect} (which is #{data.class.name}), " +
|
100
|
+
"in the context where a Hash with key of '#{param.raw_name}' " +
|
101
|
+
"was expected."
|
102
|
+
end
|
103
|
+
|
104
|
+
value = data[param.raw_name]
|
105
|
+
|
106
|
+
unless value.nil?
|
107
|
+
if param.multiplied && !value.is_a?(Array)
|
108
|
+
raise ProgrammerError,
|
109
|
+
"SOAP response tried to use '#{value.inspect}' " +
|
110
|
+
"(which is of type #{value.class.name}), as the value for " +
|
111
|
+
"'#{param.raw_name}' (which expects an Array)."
|
112
|
+
end
|
113
|
+
|
114
|
+
# Inline complex structure {:foo => {bar: ...}}
|
115
|
+
if param.struct? && !param.multiplied
|
116
|
+
result_spec[i].map = inject.call(value, param.map)
|
117
|
+
|
118
|
+
# Inline array of complex structures {:foo => [{bar: ...}]}
|
119
|
+
elsif param.struct? && param.multiplied
|
120
|
+
result_spec[i].map = value.map{|e| inject.call(e, param.map)}
|
121
|
+
|
122
|
+
# Inline scalar {:foo => :string}
|
123
|
+
else
|
124
|
+
result_spec[i].value = value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
return result_spec
|
130
|
+
}
|
131
|
+
|
132
|
+
render :template => "wash_out/#{soap_config.wsdl_style}/response",
|
133
|
+
:layout => false,
|
134
|
+
:locals => { :result => inject.call(result, @action_spec[:out]) },
|
135
|
+
:content_type => 'text/xml'
|
136
|
+
end
|
137
|
+
|
138
|
+
# This action is a fallback for all undefined SOAP actions.
|
139
|
+
def _invalid_action
|
140
|
+
render_soap_error("Cannot find SOAP action mapping for #{request.env['wash_out.soap_action']}")
|
141
|
+
end
|
142
|
+
|
143
|
+
def _catch_soap_errors
|
144
|
+
yield
|
145
|
+
rescue SOAPError => error
|
146
|
+
render_soap_error(error.message, error.code)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Render a SOAP error response.
|
150
|
+
#
|
151
|
+
# Rails do not support sequental rescue_from handling, that is, rescuing an
|
152
|
+
# exception from a rescue_from handler. Hence this function is a public API.
|
153
|
+
def render_soap_error(message, code=nil)
|
154
|
+
render :template => "wash_out/#{soap_config.wsdl_style}/error", :status => 500,
|
155
|
+
:layout => false,
|
156
|
+
:locals => { :error_message => message, :error_code => (code || 'Server') },
|
157
|
+
:content_type => 'text/xml'
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.included(controller)
|
161
|
+
controller.send :around_filter, :_catch_soap_errors
|
162
|
+
controller.send :helper, :wash_out
|
163
|
+
controller.send :before_filter, :_authenticate_wsse, :except => [
|
164
|
+
:_generate_wsdl, :_invalid_action ]
|
165
|
+
controller.send :before_filter, :_map_soap_parameters, :except => [
|
166
|
+
:_generate_wsdl, :_invalid_action ]
|
167
|
+
controller.send :skip_before_filter, :verify_authenticity_token
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.deep_select(hash, result=[], &blk)
|
171
|
+
result += Hash[hash.select(&blk)].values
|
172
|
+
|
173
|
+
hash.each do |key, value|
|
174
|
+
result = deep_select(value, result, &blk) if value.is_a? Hash
|
175
|
+
end
|
176
|
+
|
177
|
+
result
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.deep_replace_href(hash, replace)
|
181
|
+
return replace[hash[:@href]] if hash.has_key?(:@href)
|
182
|
+
|
183
|
+
hash.keys.each do |key, value|
|
184
|
+
hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
|
185
|
+
end
|
186
|
+
|
187
|
+
hash
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def action_spec
|
193
|
+
self.class.soap_actions[soap_action]
|
194
|
+
end
|
195
|
+
|
196
|
+
def request_input_tag
|
197
|
+
action_spec[:request_tag]
|
198
|
+
end
|
199
|
+
|
200
|
+
def soap_action
|
201
|
+
request.env['wash_out.soap_action']
|
202
|
+
end
|
203
|
+
|
204
|
+
def xml_data
|
205
|
+
xml_data = env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
|
206
|
+
xml_data = xml_data.values_at(:body, :Body).compact.first || {}
|
207
|
+
return xml_data if soap_config.wsdl_style == "document"
|
208
|
+
xml_data = xml_data.values_at(soap_action.underscore.to_sym, soap_action.to_sym, request_input_tag.to_sym).compact.first || {}
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
module WashOut
|
3
|
+
class Engine < ::Rails::Engine
|
4
|
+
config.wash_out = ActiveSupport::OrderedOptions.new
|
5
|
+
initializer "wash_out.configuration" do |app|
|
6
|
+
if app.config.wash_out[:catch_xml_errors]
|
7
|
+
app.config.middleware.insert_after 'ActionDispatch::ShowExceptions', WashOut::Middleware
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class WashOut::Middleware
|
2
|
+
def initialize app
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def call env
|
7
|
+
begin
|
8
|
+
@app.call env
|
9
|
+
rescue REXML::ParseException => e
|
10
|
+
self.class.raise_or_render_rexml_parse_error e, env
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.raise_or_render_rexml_parse_error e, env
|
15
|
+
raise e unless env.has_key? 'HTTP_SOAPACTION'
|
16
|
+
|
17
|
+
# Normally input would be a StringIO, but Passenger has a different API:
|
18
|
+
input = env['rack.input']
|
19
|
+
req = if input.respond_to? :string then input.string else input.read end
|
20
|
+
|
21
|
+
env['rack.errors'].puts <<-EOERR
|
22
|
+
WashOut::Exception: #{e.continued_exception} for:
|
23
|
+
#{req}
|
24
|
+
EOERR
|
25
|
+
[400, {'Content-Type' => 'text/xml'},
|
26
|
+
[render_client_soap_fault("Error parsing SOAP Request XML")]]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.render_client_soap_fault msg
|
30
|
+
xml = Builder::XmlMarkup.new
|
31
|
+
xml.tag! 'soap:Envelope', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
32
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' do
|
33
|
+
xml.tag! 'soap:Body' do
|
34
|
+
xml.tag! 'soap:Fault', :encodingStyle => 'http://schemas.xmlsoap.org/soap/encoding/' do
|
35
|
+
xml.faultcode 'Client'
|
36
|
+
xml.faultstring msg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|