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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +23 -3
  4. data/Appraisals +11 -5
  5. data/Gemfile +2 -2
  6. data/README.md +66 -2
  7. data/Rakefile +6 -7
  8. data/app/helpers/wash_out_helper.rb +49 -18
  9. data/app/views/{wash_with_soap → wash_out}/document/error.builder +0 -0
  10. data/app/views/{wash_with_soap → wash_out}/document/response.builder +1 -3
  11. data/app/views/{wash_with_soap → wash_out}/document/wsdl.builder +15 -15
  12. data/app/views/{wash_with_soap → wash_out}/rpc/error.builder +0 -0
  13. data/app/views/{wash_with_soap → wash_out}/rpc/response.builder +1 -3
  14. data/app/views/{wash_with_soap → wash_out}/rpc/wsdl.builder +16 -16
  15. data/gemfiles/rails_3.1.3.gemfile +20 -0
  16. data/gemfiles/rails_3.2.12.gemfile +20 -0
  17. data/gemfiles/rails_4.0.0.gemfile +19 -0
  18. data/gemfiles/rails_4.1.0.gemfile +19 -0
  19. data/gemfiles/rails_4.2.0.gemfile +19 -0
  20. data/lib/wash_out.rb +48 -16
  21. data/lib/wash_out/configurable.rb +41 -0
  22. data/lib/wash_out/dispatcher.rb +212 -0
  23. data/lib/wash_out/engine.rb +12 -0
  24. data/lib/wash_out/middleware.rb +41 -0
  25. data/lib/wash_out/model.rb +29 -0
  26. data/lib/wash_out/param.rb +43 -16
  27. data/lib/wash_out/router.rb +95 -0
  28. data/lib/wash_out/soap.rb +48 -0
  29. data/lib/wash_out/soap_config.rb +93 -0
  30. data/lib/wash_out/type.rb +20 -13
  31. data/lib/wash_out/version.rb +1 -1
  32. data/lib/wash_out/wsse.rb +29 -10
  33. data/spec/dummy/config/environments/test.rb +1 -0
  34. data/spec/lib/wash_out/dispatcher_spec.rb +28 -13
  35. data/spec/lib/wash_out/middleware_spec.rb +33 -0
  36. data/spec/lib/wash_out/param_spec.rb +47 -17
  37. data/spec/lib/wash_out/router_spec.rb +22 -0
  38. data/spec/lib/wash_out/type_spec.rb +9 -9
  39. data/spec/lib/wash_out_spec.rb +139 -87
  40. data/spec/spec_helper.rb +7 -1
  41. metadata +32 -25
  42. data/lib/wash_out/exceptions/programmer_error.rb +0 -10
  43. data/lib/wash_out/exceptions/soap_error.rb +0 -19
  44. data/lib/wash_out/middlewares/catcher.rb +0 -42
  45. data/lib/wash_out/middlewares/router.rb +0 -132
  46. data/lib/wash_out/rails/active_record.rb +0 -27
  47. data/lib/wash_out/rails/controller.rb +0 -201
  48. data/lib/wash_out/rails/engine.rb +0 -74
  49. 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 => "../"
@@ -1,21 +1,53 @@
1
- require 'wash_out/exceptions/programmer_error'
2
- require 'wash_out/exceptions/soap_error'
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
- require 'wash_out/middlewares/router'
5
- require 'wash_out/middlewares/catcher'
14
+ module WashOut
15
+ def self.root
16
+ File.expand_path '../..', __FILE__
17
+ end
18
+ end
6
19
 
7
- require 'wash_out/wsse'
8
- require 'wash_out/type'
9
- require 'wash_out/param'
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
- require 'wash_out/rails/active_record'
12
- require 'wash_out/rails/controller'
13
- require 'wash_out/rails/engine'
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
- module WashOut
16
- def self.normalize(string, config)
17
- return string.to_s if !config || !config.camelize_wsdl
18
- return string.to_s.camelize(:lower) if config.camelize_wsdl == 'lower'
19
- return string.to_s.camelize
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