wash_out 0.9.2 → 0.10.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.travis.yml +0 -1
  4. data/Appraisals +1 -9
  5. data/Gemfile +1 -1
  6. data/README.md +2 -28
  7. data/app/views/wash_with_soap/document/response.builder +3 -1
  8. data/app/views/wash_with_soap/document/wsdl.builder +3 -3
  9. data/app/views/wash_with_soap/rpc/response.builder +3 -1
  10. data/app/views/wash_with_soap/rpc/wsdl.builder +3 -3
  11. data/lib/wash_out/exceptions/programmer_error.rb +10 -0
  12. data/lib/wash_out/exceptions/soap_error.rb +19 -0
  13. data/lib/wash_out/middlewares/catcher.rb +42 -0
  14. data/lib/wash_out/middlewares/router.rb +132 -0
  15. data/lib/wash_out/param.rb +15 -30
  16. data/lib/wash_out/rails/active_record.rb +27 -0
  17. data/lib/wash_out/rails/controller.rb +201 -0
  18. data/lib/wash_out/rails/engine.rb +74 -0
  19. data/lib/wash_out/type.rb +13 -20
  20. data/lib/wash_out/version.rb +1 -1
  21. data/lib/wash_out/wsse.rb +7 -26
  22. data/lib/wash_out.rb +16 -42
  23. data/spec/lib/wash_out/dispatcher_spec.rb +6 -21
  24. data/spec/lib/wash_out/param_spec.rb +10 -8
  25. data/spec/lib/wash_out/rack_spec.rb +55 -0
  26. data/spec/lib/wash_out_spec.rb +3 -40
  27. data/spec/spec_helper.rb +1 -5
  28. metadata +19 -21
  29. data/lib/wash_out/configurable.rb +0 -41
  30. data/lib/wash_out/dispatcher.rb +0 -202
  31. data/lib/wash_out/engine.rb +0 -12
  32. data/lib/wash_out/middleware.rb +0 -41
  33. data/lib/wash_out/model.rb +0 -29
  34. data/lib/wash_out/router.rb +0 -95
  35. data/lib/wash_out/soap.rb +0 -47
  36. data/lib/wash_out/soap_config.rb +0 -93
  37. data/spec/lib/wash_out/middleware_spec.rb +0 -33
  38. data/spec/lib/wash_out/router_spec.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9149b2f9a1a04ad8d6ba859ef68f2dbf9db6d585
4
- data.tar.gz: 55936871f904cf8d45517312b4479a39a5a5e5d5
3
+ metadata.gz: 443e624e950f3d062ccc19a9d2221dbc1ac91b25
4
+ data.tar.gz: facd37fd93991475d04e93a3544e156db1736a2c
5
5
  SHA512:
6
- metadata.gz: 5e2798c550e16322bb72de0dfa08c0e3a18ec3d292b481fc905d36f72d4b51a60bd889a583b723fc54081a484520c260a07e8b28dab1bb9babfa10f115e76e4b
7
- data.tar.gz: 08821de12b76c9fc9f4c79ddc381279ebddfa7c42d6a197e9a7b7528f94044b46927edffab4bf9bb7dfcb090e4678effe80ef1e61cfd4cdee6c339dd0ed64090
6
+ metadata.gz: c7e405ff31de4bdae9c2927e28791167a36a942a999a1eb732fcaa459855b5b1f227e30ab71d784a4efd6ec0b4a10d5d23b170dba8c27287c71b7d1d4aeab9f9
7
+ data.tar.gz: 6874a42fc4db8368b1cbb7fff4989ce5741449f743d2924b234be4da80762ac4688a4354aa5a5e245eb0d067b249c9dd01ceabe82239f0c05e384ba978cf2dc5
data/.gitignore CHANGED
@@ -2,8 +2,6 @@
2
2
  .bundle/
3
3
  *.gem
4
4
  .idea/
5
- .ruby-gemset
6
- .ruby-version
7
5
  .rvmrc
8
6
  *.swp
9
7
  log/*.log
data/.travis.yml CHANGED
@@ -4,4 +4,3 @@ rvm:
4
4
  - 1.9.3
5
5
  - jruby-19mode
6
6
  - 2.0.0
7
- - 2.1.0
data/Appraisals CHANGED
@@ -12,12 +12,4 @@ end
12
12
 
13
13
  appraise "rails-4.0.0" do
14
14
  gem "rails", "4.0.0"
15
- end
16
-
17
- appraise "rails-4.1.0" do
18
- gem "rails", "4.1.0"
19
- end
20
-
21
- appraise "rails-4.2.0" do
22
- gem "rails", "4.2.0"
23
- end
15
+ end
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ gemspec
4
4
 
5
5
  gem 'wasabi'
6
6
  gem 'savon', '>= 2.0.0'
7
- gem 'httpi'
7
+ gem 'httpi', :git => 'git://github.com/savonrb/httpi.git'
8
8
 
9
9
  gem 'rspec-rails'
10
10
  gem 'guard'
data/README.md CHANGED
@@ -63,7 +63,7 @@ class RumbasController < ApplicationController
63
63
  def add_circle
64
64
  circle = params[:circle]
65
65
 
66
- raise SOAPError, "radius is too small" if circle[:radius] < 3.0
66
+ raise WashOut::SOAPError, "radius is too small" if circle[:radius] < 3.0
67
67
 
68
68
  Circle.new(circle[:center][:x], circle[:center][:y], circle[:radius])
69
69
 
@@ -94,7 +94,7 @@ WashOutSample::Application.routes.draw do
94
94
  end
95
95
  ```
96
96
 
97
- In such a setup, the generated WSDL may be queried at path `/rumbas/wsdl`. So, with a
97
+ In such a setup, the generated WSDL may be queried at path `/api/wsdl`. So, with a
98
98
  gem like Savon, a request can be done using this path:
99
99
 
100
100
  ```ruby
@@ -137,32 +137,6 @@ To use defined type inside your inline declaration, pass the class instead of ty
137
137
  Note that WashOut extends the `ActiveRecord` so every model you use is already a WashOut::Type and can be used
138
138
  inside your interface declarations.
139
139
 
140
- ## WSSE Authentication
141
-
142
- WashOut provides two mechanism for WSSE Authentication.
143
-
144
- ### Static Authentication
145
-
146
- You can configure the service to validate against a username and password with the following configuration:
147
-
148
- ```ruby
149
- soap_service namespace: "wash_out", wsse_username: "username", wsse_password: "password"
150
- ```
151
-
152
- With this mechanism, all the actions in the controller will be authenticated against the specified username and password. If you need to authenticate different users, you can use the dynamic mechanism described below.
153
-
154
- ### Dynamic Authentication
155
-
156
- Dynamic authentication allows you to process the username and password any way you want, with the most common case being authenticating against a database. The configuration option for this mechanism is called `wsse_auth_callback`:
157
-
158
- ```ruby
159
- soap_service namespace: "wash_out", wsse_auth_callback: ->(username, password) {
160
- return !User.find_by(username: username).authenticate(password).blank?
161
- }
162
- ```
163
-
164
- Keep in mind that the password may already be hashed by the SOAP client, so you would have to check against that condition too as per [spec](http://www.oasis-open.org/committees/download.php/16782/wss-v1.1-spec-os-UsernameTokenProfile.pdf)
165
-
166
140
  ## Configuration
167
141
 
168
142
  Use `config.wash_out...` inside your environment configuration to setup WashOut globally.
@@ -3,7 +3,9 @@ xml.tag! "soap:Envelope", "xmlns:soap" => 'http://schemas.xmlsoap.org/soap/envel
3
3
  "xmlns:xsd" => 'http://www.w3.org/2001/XMLSchema',
4
4
  "xmlns:tns" => @namespace do
5
5
  xml.tag! "soap:Body" do
6
- xml.tag! "tns:#{@action_spec[:response_tag]}" do
6
+ key = "tns:#{@operation}#{controller.soap_config.camelize_wsdl ? 'Response' : '_response'}"
7
+
8
+ xml.tag! @action_spec[:response_tag] || key do
7
9
  wsdl_data xml, result
8
10
  end
9
11
  end
@@ -20,10 +20,10 @@ xml.definitions 'xmlns' => 'http://schemas.xmlsoap.org/wsdl/',
20
20
  end
21
21
 
22
22
  xml.portType :name => "#{@name}_port" do
23
- @map.each do |operation, formats|
23
+ @map.keys.each do |operation|
24
24
  xml.operation :name => operation do
25
25
  xml.input :message => "tns:#{operation}"
26
- xml.output :message => "tns:#{formats[:response_tag]}"
26
+ xml.output :message => "tns:#{operation}#{controller.soap_config.camelize_wsdl ? 'Response' : '_response'}"
27
27
  end
28
28
  end
29
29
  end
@@ -59,7 +59,7 @@ xml.definitions 'xmlns' => 'http://schemas.xmlsoap.org/wsdl/',
59
59
  xml.part wsdl_occurence(p, false, :name => p.name, :type => p.namespaced_type)
60
60
  end
61
61
  end
62
- xml.message :name => formats[:response_tag] do
62
+ xml.message :name => "#{operation}#{controller.soap_config.camelize_wsdl ? 'Response' : '_response'}" do
63
63
  formats[:out].each do |p|
64
64
  xml.part wsdl_occurence(p, false, :name => p.name, :type => p.namespaced_type)
65
65
  end
@@ -4,7 +4,9 @@ xml.tag! "soap:Envelope", "xmlns:soap" => 'http://schemas.xmlsoap.org/soap/envel
4
4
  "xmlns:xsi" => 'http://www.w3.org/2001/XMLSchema-instance',
5
5
  "xmlns:tns" => @namespace do
6
6
  xml.tag! "soap:Body" do
7
- xml.tag! "tns:#{@action_spec[:response_tag]}" do
7
+ key = "tns:#{@operation}#{controller.soap_config.camelize_wsdl ? 'Response' : '_response'}"
8
+
9
+ xml.tag! @action_spec[:response_tag] || key do
8
10
  wsdl_data xml, result
9
11
  end
10
12
  end
@@ -20,10 +20,10 @@ xml.definitions 'xmlns' => 'http://schemas.xmlsoap.org/wsdl/',
20
20
  end
21
21
 
22
22
  xml.portType :name => "#{@name}_port" do
23
- @map.each do |operation, formats|
23
+ @map.keys.each do |operation|
24
24
  xml.operation :name => operation do
25
25
  xml.input :message => "tns:#{operation}"
26
- xml.output :message => "tns:#{formats[:response_tag]}"
26
+ xml.output :message => "tns:#{operation}#{controller.soap_config.camelize_wsdl ? 'Response' : '_response'}"
27
27
  end
28
28
  end
29
29
  end
@@ -59,7 +59,7 @@ xml.definitions 'xmlns' => 'http://schemas.xmlsoap.org/wsdl/',
59
59
  xml.part wsdl_occurence(p, true, :name => p.name, :type => p.namespaced_type)
60
60
  end
61
61
  end
62
- xml.message :name => formats[:response_tag] do
62
+ xml.message :name => "#{operation}#{controller.soap_config.camelize_wsdl ? 'Response' : '_response'}" do
63
63
  formats[:out].each do |p|
64
64
  xml.part wsdl_occurence(p, true, :name => p.name, :type => p.namespaced_type)
65
65
  end
@@ -0,0 +1,10 @@
1
+ module WashOut
2
+ #
3
+ # An exception reflection unexpected error
4
+ #
5
+ # Used internally to remark badly composited definitions of
6
+ # actions and other violations
7
+ #
8
+ class ProgrammerError < Exception
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module WashOut
2
+ #
3
+ # An exception reflecting expected logical error
4
+ #
5
+ # Such exception (or its descendants) will be intercepted and rendered
6
+ # into proper SOAP error XML response.
7
+ #
8
+ class SOAPError < Exception
9
+ attr_accessor :code
10
+
11
+ def initialize(message, code=nil)
12
+ super(message)
13
+ @code = code
14
+ end
15
+ end
16
+ end
17
+
18
+ # Backward compatibility hack
19
+ class SOAPError < WashOut::SOAPError; end
@@ -0,0 +1,42 @@
1
+ module WashOut
2
+ module Middlewares
3
+ class Catcher
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @app.call env
10
+ rescue REXML::ParseException => e
11
+ raise e unless env.has_key?('HTTP_SOAPACTION')
12
+
13
+ input = env['rack.input'].respond_to?(:string) ? env['rack.input'].string
14
+ : env['rack.input'].read
15
+
16
+ env['rack.errors'].puts <<-TEXT
17
+ WashOut::Exception: #{e.continued_exception} for:
18
+ #{input}
19
+ TEXT
20
+
21
+ [
22
+ 400,
23
+ {'Content-Type' => 'text/xml'},
24
+ [self.class.render_client_soap_fault("Error parsing SOAP Request XML")]
25
+ ]
26
+ end
27
+
28
+ def self.render_client_soap_fault(msg)
29
+ xml = Builder::XmlMarkup.new
30
+ xml.tag! 'soap:Envelope', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
31
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' do
32
+ xml.tag! 'soap:Body' do
33
+ xml.tag! 'soap:Fault', :encodingStyle => 'http://schemas.xmlsoap.org/soap/encoding/' do
34
+ xml.faultcode 'Client'
35
+ xml.faultstring msg
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,132 @@
1
+ require 'nori'
2
+
3
+ module WashOut
4
+ module Middlewares
5
+ #
6
+ # This class is a Rack middleware used to route SOAP requests to a proper
7
+ # action of a given SOAP controller.
8
+ #
9
+ # WashOut engine adds routing helper `wash_out` that registeres this middleware
10
+ # as a main responder internally.
11
+ #
12
+ # @see WashOut::Rails::Engine
13
+ #
14
+ class Router
15
+ def initialize(controller_name)
16
+ @controller_name = "#{controller_name.to_s}_controller".camelize
17
+ end
18
+
19
+ def parse_soap_action(env)
20
+ return env['wash_out.soap_action'] if env['wash_out.soap_action']
21
+
22
+ # SOAPACTION is expected to come in quotes that have to be stripped
23
+ soap_action = env['HTTP_SOAPACTION'].to_s.gsub(/^"(.*)"$/, '\1')
24
+
25
+ # Clients can sometimes send empty SOAPACTION to avoid duplication
26
+ # In this cases we have to get it from the body
27
+ if soap_action.blank?
28
+ soap_action = nori.parse(soap_body env)[:Envelope][:Body].keys.first.to_s
29
+ end
30
+
31
+ # RUBY18
32
+ soap_action.force_encoding('UTF-8') if soap_action.respond_to? :force_encoding
33
+
34
+ # And finally we have to strip namespace from the action name
35
+ if @controller.soap_config.namespace
36
+ namespace = Regexp.escape @controller.soap_config.namespace.to_s
37
+ soap_action.gsub!(/^(#{namespace}(\/|#)?)?([^"]*)$/, '\3')
38
+ end
39
+
40
+ env['wash_out.soap_action'] = soap_action
41
+ end
42
+
43
+ def parse_soap_parameters(env)
44
+ return env['wash_out.soap_data'] if env['wash_out.soap_data']
45
+
46
+ env['wash_out.soap_data'] = nori(@controller.soap_config.snakecase_input).parse(soap_body env)
47
+
48
+ # Seeking for in-XML substitutions marked by attribute `id`
49
+ references = self.class.deep_select(env['wash_out.soap_data']) do |k,v|
50
+ v.is_a?(Hash) && v.has_key?(:@id)
51
+ end
52
+
53
+ # Replacing the found substitutions with nodes having proper `href` attribute
54
+ unless references.blank?
55
+ replaces = {}
56
+ references.each { |r| replaces['#'+r[:@id]] = r }
57
+
58
+ env['wash_out.soap_data'] = self.class.deep_replace_href(env['wash_out.soap_data'], replaces)
59
+ end
60
+
61
+ env['wash_out.soap_data']
62
+ end
63
+
64
+ #
65
+ # Nori parser builder
66
+ #
67
+ def nori(snakecase=false)
68
+ Nori.new(
69
+ parser: @controller.soap_config.parser,
70
+ strip_namespaces: true,
71
+ advanced_typecasting: true,
72
+ convert_tags_to: (
73
+ snakecase ? lambda { |tag| tag.snakecase.to_sym }
74
+ : lambda { |tag| tag.to_sym }
75
+ )
76
+ )
77
+ end
78
+
79
+ #
80
+ # Universal body accessor working with any Rack server
81
+ #
82
+ def soap_body(env)
83
+ env['rack.input'].respond_to?(:string) ? env['rack.input'].string
84
+ : env['rack.input'].read
85
+ end
86
+
87
+ def call(env)
88
+ @controller = @controller_name.constantize
89
+
90
+ parse_soap_action(env)
91
+ parse_soap_parameters(env)
92
+
93
+ action_spec = @controller.soap_actions[env['wash_out.soap_action']]
94
+
95
+ if action_spec
96
+ action = action_spec[:to]
97
+ else
98
+ action = '_invalid_action'
99
+ end
100
+
101
+ @controller.action(action).call(env)
102
+ end
103
+
104
+ #
105
+ # Recursively seeks XML tree for nodes matching given block
106
+ #
107
+ def self.deep_select(hash, result=[], &blk)
108
+ result += Hash[hash.select(&blk)].values
109
+
110
+ hash.each do |key, value|
111
+ result = deep_select(value, result, &blk) if value.is_a? Hash
112
+ end
113
+
114
+ result
115
+ end
116
+
117
+ #
118
+ # Recursively replaces nodes in the tree matching the given
119
+ # map of `href` attributes
120
+ #
121
+ def self.deep_replace_href(hash, replace)
122
+ return replace[hash[:@href]] if hash.has_key?(:@href)
123
+
124
+ hash.keys.each do |key, value|
125
+ hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
126
+ end
127
+
128
+ hash
129
+ end
130
+ end
131
+ end
132
+ end
@@ -13,26 +13,22 @@ module WashOut
13
13
  def initialize(soap_config, name, type, multiplied = false)
14
14
  type ||= {}
15
15
  @soap_config = soap_config
16
- @name = name.to_s
16
+ @name = WashOut.normalize(name, soap_config)
17
17
  @raw_name = name.to_s
18
18
  @map = {}
19
19
  @multiplied = multiplied
20
20
 
21
- if soap_config.camelize_wsdl.to_s == 'lower'
22
- @name = @name.camelize(:lower)
23
- elsif soap_config.camelize_wsdl
24
- @name = @name.camelize
25
- end
26
-
27
21
  if type.is_a?(Symbol)
28
22
  @type = type.to_s
29
23
  elsif type.is_a?(Class)
30
24
  @type = 'struct'
31
25
  @map = self.class.parse_def(soap_config, type.wash_out_param_map)
32
26
  @source_class = type
33
- else
27
+ elsif type.is_a?(Hash)
34
28
  @type = 'struct'
35
29
  @map = self.class.parse_def(soap_config, type)
30
+ else
31
+ raise RuntimeError, "Wrong definition: #{type.inspect}"
36
32
  end
37
33
  end
38
34
 
@@ -40,7 +36,7 @@ module WashOut
40
36
  # Hash, to a native Ruby object according to the definition of this type.
41
37
  def load(data, key)
42
38
  if !data.has_key? key
43
- raise WashOut::Dispatcher::SOAPError, "Required SOAP parameter '#{key}' is missing"
39
+ raise WashOut::SOAPError, "Required SOAP parameter '#{key}' is missing"
44
40
  end
45
41
 
46
42
  data = data[key]
@@ -84,7 +80,7 @@ module WashOut
84
80
  operation.call(data)
85
81
  end
86
82
  rescue
87
- raise WashOut::Dispatcher::SOAPError, "Invalid SOAP parameter '#{key}' format"
83
+ raise WashOut::SOAPError, "Invalid SOAP parameter '#{key}' format"
88
84
  end
89
85
  end
90
86
  end
@@ -134,33 +130,22 @@ module WashOut
134
130
  raise RuntimeError, "[] should not be used in your params. Use nil if you want to mark empty set." if definition == []
135
131
  return [] if definition == nil
136
132
 
137
- if definition.is_a?(Class) && definition.ancestors.include?(WashOut::Type)
138
- definition = definition.wash_out_param_map
139
- end
133
+ definition = { :value => definition } unless definition.is_a?(Hash)
140
134
 
141
- if [Array, Symbol].include?(definition.class)
142
- definition = { :value => definition }
143
- end
144
-
145
- if definition.is_a? Hash
146
- definition.map do |name, opt|
147
- if opt.is_a? WashOut::Param
148
- opt
149
- elsif opt.is_a? Array
150
- WashOut::Param.new(soap_config, name, opt[0], true)
151
- else
152
- WashOut::Param.new(soap_config, name, opt)
153
- end
135
+ definition.map do |name, opt|
136
+ if opt.is_a? WashOut::Param
137
+ opt
138
+ elsif opt.is_a? Array
139
+ WashOut::Param.new(soap_config, name, opt[0], true)
140
+ else
141
+ WashOut::Param.new(soap_config, name, opt)
154
142
  end
155
- else
156
- raise RuntimeError, "Wrong definition: #{definition.inspect}"
157
143
  end
158
144
  end
159
145
 
160
146
  def flat_copy
161
147
  copy = self.class.new(@soap_config, @name, @type.to_sym, @multiplied)
162
148
  copy.raw_name = raw_name
163
- copy.source_class = copy.source_class
164
149
  copy
165
150
  end
166
151
 
@@ -169,7 +154,7 @@ module WashOut
169
154
  # Used to load an entire structure.
170
155
  def map_struct(data)
171
156
  unless data.is_a?(Hash)
172
- raise WashOut::Dispatcher::SOAPError, "SOAP message structure is broken"
157
+ raise WashOut::SOAPError, "SOAP message structure is broken"
173
158
  end
174
159
 
175
160
  data = data.with_indifferent_access
@@ -0,0 +1,27 @@
1
+ module WashOut
2
+ module Rails
3
+ module ActiveRecord
4
+ def wash_out_param_map
5
+ types = {
6
+ text: :string,
7
+ float: :double,
8
+ decimal: :double,
9
+ timestamp: :string
10
+ }
11
+ map = {}
12
+
13
+ columns_hash.each do |key, column|
14
+ type = column.type
15
+ type = types[type] if types.has_key?(type)
16
+ map[key] = type
17
+ end
18
+
19
+ map
20
+ end
21
+
22
+ def wash_out_param_name(soap_config = nil)
23
+ WashOut.normalize(name.underscore.gsub('/', '.'), soap_config)
24
+ end
25
+ end
26
+ end
27
+ end