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.
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