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.
- checksums.yaml +4 -4
- data/.gitignore +0 -2
- data/.travis.yml +0 -1
- data/Appraisals +1 -9
- data/Gemfile +1 -1
- data/README.md +2 -28
- data/app/views/wash_with_soap/document/response.builder +3 -1
- data/app/views/wash_with_soap/document/wsdl.builder +3 -3
- data/app/views/wash_with_soap/rpc/response.builder +3 -1
- data/app/views/wash_with_soap/rpc/wsdl.builder +3 -3
- data/lib/wash_out/exceptions/programmer_error.rb +10 -0
- data/lib/wash_out/exceptions/soap_error.rb +19 -0
- data/lib/wash_out/middlewares/catcher.rb +42 -0
- data/lib/wash_out/middlewares/router.rb +132 -0
- data/lib/wash_out/param.rb +15 -30
- data/lib/wash_out/rails/active_record.rb +27 -0
- data/lib/wash_out/rails/controller.rb +201 -0
- data/lib/wash_out/rails/engine.rb +74 -0
- data/lib/wash_out/type.rb +13 -20
- data/lib/wash_out/version.rb +1 -1
- data/lib/wash_out/wsse.rb +7 -26
- data/lib/wash_out.rb +16 -42
- data/spec/lib/wash_out/dispatcher_spec.rb +6 -21
- data/spec/lib/wash_out/param_spec.rb +10 -8
- data/spec/lib/wash_out/rack_spec.rb +55 -0
- data/spec/lib/wash_out_spec.rb +3 -40
- data/spec/spec_helper.rb +1 -5
- metadata +19 -21
- data/lib/wash_out/configurable.rb +0 -41
- data/lib/wash_out/dispatcher.rb +0 -202
- data/lib/wash_out/engine.rb +0 -12
- data/lib/wash_out/middleware.rb +0 -41
- data/lib/wash_out/model.rb +0 -29
- data/lib/wash_out/router.rb +0 -95
- data/lib/wash_out/soap.rb +0 -47
- data/lib/wash_out/soap_config.rb +0 -93
- data/spec/lib/wash_out/middleware_spec.rb +0 -33
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 443e624e950f3d062ccc19a9d2221dbc1ac91b25
|
4
|
+
data.tar.gz: facd37fd93991475d04e93a3544e156db1736a2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7e405ff31de4bdae9c2927e28791167a36a942a999a1eb732fcaa459855b5b1f227e30ab71d784a4efd6ec0b4a10d5d23b170dba8c27287c71b7d1d4aeab9f9
|
7
|
+
data.tar.gz: 6874a42fc4db8368b1cbb7fff4989ce5741449f743d2924b234be4da80762ac4688a4354aa5a5e245eb0d067b249c9dd01ceabe82239f0c05e384ba978cf2dc5
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
data/Gemfile
CHANGED
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 `/
|
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
|
-
|
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
|
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:#{
|
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 =>
|
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
|
-
|
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
|
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:#{
|
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 =>
|
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,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
|
data/lib/wash_out/param.rb
CHANGED
@@ -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
|
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
|
-
|
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::
|
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::
|
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
|
-
|
138
|
-
definition = definition.wash_out_param_map
|
139
|
-
end
|
133
|
+
definition = { :value => definition } unless definition.is_a?(Hash)
|
140
134
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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::
|
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
|