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