dzl 1.0.0.rc9 → 1.0.0.rc10
Sign up to get free protection for your applications and to get access to all the features.
- data/config.ru +8 -8
- data/lib/dzl/core_ext.rb +12 -2
- data/lib/dzl/dsl_proxies/parameter.rb +0 -4
- data/lib/dzl/dsl_proxies/protection.rb +5 -1
- data/lib/dzl/dsl_subjects/parameter_block.rb +3 -0
- data/lib/dzl/dsl_subjects/protection.rb +25 -7
- data/lib/dzl/dsl_subjects/router.rb +6 -1
- data/lib/dzl/examples/fun_with_hashes.rb +29 -0
- data/lib/dzl/examples/fun_with_params.rb +6 -0
- data/lib/dzl/rack_interface.rb +11 -1
- data/lib/dzl/version.rb +1 -1
- data/lib/hash_validator/hash_validator.rb +10 -1
- data/spec/fun_with_hashes_spec.rb +45 -10
- data/spec/fun_with_params_spec.rb +17 -0
- data/spec/hash_validator_spec.rb +34 -0
- metadata +5 -5
data/config.ru
CHANGED
@@ -30,10 +30,10 @@ end
|
|
30
30
|
# run Dzl::Examples::RouteProfile
|
31
31
|
# end
|
32
32
|
|
33
|
-
require 'dzl/examples/fun_with_handlers'
|
34
|
-
map '/' do
|
35
|
-
|
36
|
-
end
|
33
|
+
# require 'dzl/examples/fun_with_handlers'
|
34
|
+
# map '/' do
|
35
|
+
# run Dzl::Examples::FunWithHandlers
|
36
|
+
# end
|
37
37
|
|
38
38
|
# require 'dzl/examples/fun_with_hooks'
|
39
39
|
# map '/' do
|
@@ -45,10 +45,10 @@ end
|
|
45
45
|
# run Dzl::Examples::FunWithScopes
|
46
46
|
# end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
require 'dzl/examples/fun_with_hashes'
|
49
|
+
map '/' do
|
50
|
+
run Dzl::Examples::FunWithHashes
|
51
|
+
end
|
52
52
|
|
53
53
|
# require 'dzl/examples/fun_with_pblocks'
|
54
54
|
# map '/' do
|
data/lib/dzl/core_ext.rb
CHANGED
@@ -6,10 +6,20 @@ class Hash
|
|
6
6
|
def recursively_symbolize_keys!
|
7
7
|
self.symbolize_keys!
|
8
8
|
self.values.each do |v|
|
9
|
-
if v.is_a?(Hash)
|
9
|
+
if v.is_a?(Hash) || v.is_a?(Array)
|
10
10
|
v.recursively_symbolize_keys!
|
11
11
|
end
|
12
12
|
end
|
13
13
|
self
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Array
|
18
|
+
def recursively_symbolize_keys!
|
19
|
+
self.each do |item|
|
20
|
+
if item.is_a?(Hash) || item.is_a?(Array)
|
21
|
+
item.recursively_symbolize_keys!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -48,10 +48,6 @@ class Dzl::DSLProxies::Parameter < Dzl::DSLProxy
|
|
48
48
|
end
|
49
49
|
|
50
50
|
if type == Hash
|
51
|
-
unless @subject.router.subject(2).is_a?(Dzl::DSLSubjects::Endpoint)
|
52
|
-
raise Dzl::NYI.new("Hash parameters may only be specified in endpoints, for now")
|
53
|
-
end
|
54
|
-
|
55
51
|
raise Dzl::RetryBlockPlease.new(
|
56
52
|
subject: HashValidator.new(
|
57
53
|
@subject.opts.except(:type_opts).reverse_merge(format: :json)
|
@@ -3,4 +3,8 @@ class Dzl::DSLProxies::Protection < Dzl::DSLProxy
|
|
3
3
|
raise ArgumentError unless [:username, :password].all? {|k| opts[k].present?}
|
4
4
|
@subject.opts[:http_basic] = opts
|
5
5
|
end
|
6
|
-
|
6
|
+
def api_key(opts)
|
7
|
+
raise ArgumentError unless [:header, :valid_keys].all? {|k| opts[k].present?}
|
8
|
+
@subject.opts[:api_key] = opts
|
9
|
+
end
|
10
|
+
end
|
@@ -21,6 +21,9 @@ class Dzl::DSLSubjects::ParameterBlock < Dzl::DSLSubject
|
|
21
21
|
if param.opts[:type] == Hash
|
22
22
|
param = if parandidate.nil? && param.opts[:required]
|
23
23
|
Dzl::ValueOrError.new(e: param.opts[:header] ? :missing_required_header : :missing_required_param)
|
24
|
+
elsif parandidate.nil?
|
25
|
+
# TODO HashValidator with default values
|
26
|
+
Dzl::ValueOrError.new(v: :__no_value__)
|
24
27
|
else
|
25
28
|
unless parandidate.is_a?(Hash)
|
26
29
|
if param.opts[:format] == :json
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'dzl/dsl_proxies/protection'
|
2
2
|
|
3
3
|
class Dzl::RespondWithHTTPBasicChallenge < StandardError; end
|
4
|
+
class Dzl::RespondWithInvalidAPIKey < StandardError; end
|
4
5
|
|
5
6
|
class Dzl::DSLSubjects::Protection < Dzl::DSLSubject
|
6
7
|
def initialize
|
@@ -15,17 +16,34 @@ class Dzl::DSLSubjects::Protection < Dzl::DSLSubject
|
|
15
16
|
if @opts[:http_basic].present?
|
16
17
|
@auth = Rack::Auth::Basic::Request.new(request.env)
|
17
18
|
if @auth.provided? && @auth.basic? && @auth.credentials
|
19
|
+
# Invalid basic auth credentials
|
18
20
|
unless @auth.credentials[0] == @opts[:http_basic][:username] &&
|
19
21
|
@auth.credentials[1] == @opts[:http_basic][:password]
|
20
|
-
Dzl::ValueOrError.new(e: :invalid_http_basic_credentials)
|
21
|
-
else
|
22
|
-
Dzl::ValueOrError.new(v: nil)
|
22
|
+
return Dzl::ValueOrError.new(e: :invalid_http_basic_credentials)
|
23
23
|
end
|
24
|
+
# No basic auth credentials provided
|
24
25
|
else
|
25
|
-
Dzl::ValueOrError.new(e: :no_http_basic_credentials)
|
26
|
+
return Dzl::ValueOrError.new(e: :no_http_basic_credentials)
|
26
27
|
end
|
27
|
-
else
|
28
|
-
Dzl::ValueOrError.new(v: nil)
|
29
28
|
end
|
29
|
+
|
30
|
+
if @opts[:api_key].present?
|
31
|
+
api_key_header = @opts[:api_key][:header]
|
32
|
+
allowed_keys = @opts[:api_key][:valid_keys]
|
33
|
+
request_key = request.headers[api_key_header]
|
34
|
+
|
35
|
+
if request_key
|
36
|
+
# Invalid API key provided
|
37
|
+
unless allowed_keys.include? request_key
|
38
|
+
return Dzl::ValueOrError.new(e: :invalid_api_key)
|
39
|
+
end
|
40
|
+
# No API key provided
|
41
|
+
else
|
42
|
+
return Dzl::ValueOrError.new(e: :no_api_key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Auth passed
|
47
|
+
return Dzl::ValueOrError.new(v: nil)
|
30
48
|
end
|
31
|
-
end
|
49
|
+
end
|
@@ -99,7 +99,12 @@ class Dzl::DSLSubjects::Router < Dzl::DSLSubject
|
|
99
99
|
raise Dzl::RespondWithHTTPBasicChallenge
|
100
100
|
end
|
101
101
|
|
102
|
+
if !errors.empty? &&
|
103
|
+
errors.values.any? {|v| v == :no_api_key || v == :invalid_api_key}
|
104
|
+
raise Dzl::RespondWithInvalidAPIKey
|
105
|
+
end
|
106
|
+
|
102
107
|
endpoint || raise(Dzl::NotFound.new(errors))
|
103
108
|
end
|
104
109
|
|
105
|
-
end
|
110
|
+
end
|
@@ -28,4 +28,33 @@ class Dzl::Examples::FunWithHashes < Dzl::Examples::Base
|
|
28
28
|
allowed_values %w{one two three}
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
pblock :ingredients do
|
33
|
+
required :ingredients do
|
34
|
+
type Hash
|
35
|
+
required :cheese, :meat, :bread
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
post '/boring_sandwich' do
|
40
|
+
import_pblock :ingredients
|
41
|
+
end
|
42
|
+
|
43
|
+
post '/awesome_sandwich' do
|
44
|
+
import_pblock :ingredients
|
45
|
+
parameter :ingredients do
|
46
|
+
required(:meat) { type Array }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
post '/another_boring_sandwich' do
|
51
|
+
import_pblock :ingredients
|
52
|
+
end
|
53
|
+
|
54
|
+
post '/elis_bug' do
|
55
|
+
optional :hash do
|
56
|
+
type Hash
|
57
|
+
optional(:id) { type Fixnum }
|
58
|
+
end
|
59
|
+
end
|
31
60
|
end
|
data/lib/dzl/rack_interface.rb
CHANGED
@@ -15,6 +15,8 @@ module Dzl::RackInterface
|
|
15
15
|
[__router.handle_request(request), nil]
|
16
16
|
rescue Dzl::RespondWithHTTPBasicChallenge
|
17
17
|
[respond_with_http_basic_challenge, nil]
|
18
|
+
rescue Dzl::RespondWithInvalidAPIKey
|
19
|
+
[respond_with_invalid_api_key, nil]
|
18
20
|
rescue Dzl::Error => e
|
19
21
|
[respond_with_dzl_error_handler(e), nil]
|
20
22
|
rescue StandardError => e
|
@@ -51,6 +53,14 @@ module Dzl::RackInterface
|
|
51
53
|
response.finish
|
52
54
|
end
|
53
55
|
|
56
|
+
def respond_with_invalid_api_key
|
57
|
+
response = Rack::Response.new
|
58
|
+
response.status = 401
|
59
|
+
response.headers['Content-Type'] = 'text/html'
|
60
|
+
response.write("Not Authorized\n")
|
61
|
+
response.finish
|
62
|
+
end
|
63
|
+
|
54
64
|
def respond_with_standard_error_handler(e)
|
55
65
|
response = Rack::Response.new
|
56
66
|
response.headers['Content-Type'] = 'application/json'
|
@@ -108,4 +118,4 @@ module Dzl::RackInterface
|
|
108
118
|
end
|
109
119
|
end
|
110
120
|
end
|
111
|
-
end
|
121
|
+
end
|
data/lib/dzl/version.rb
CHANGED
@@ -24,6 +24,16 @@ class HashValidator
|
|
24
24
|
@template[:opts]
|
25
25
|
end
|
26
26
|
|
27
|
+
def overwrite_opts(opts)
|
28
|
+
@template[:opts].merge(opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
def clone
|
32
|
+
copy = HashValidator.new
|
33
|
+
copy.instance_variable_set(:@template, Marshal.load(Marshal.dump(@template)))
|
34
|
+
copy
|
35
|
+
end
|
36
|
+
|
27
37
|
def valid?(hsh)
|
28
38
|
return false unless hsh.keys.all? { |key| top[:keys].include?(key) }
|
29
39
|
|
@@ -71,7 +81,6 @@ class HashValidator
|
|
71
81
|
def key(k, opts, &block)
|
72
82
|
top[:keys][k] = {
|
73
83
|
opts: opts,
|
74
|
-
block: block,
|
75
84
|
keys: {}
|
76
85
|
}
|
77
86
|
|
@@ -29,16 +29,6 @@ describe 'hash parameters' do
|
|
29
29
|
}.to raise_exception(Dzl::Deprecated)
|
30
30
|
end
|
31
31
|
|
32
|
-
specify 'NYI: hash parameters are currently only allowed in anonymous pblocks' do
|
33
|
-
expect {
|
34
|
-
class T1 < Dzl::Examples::Base
|
35
|
-
pblock :broken do
|
36
|
-
required(:foo) { type Hash }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
}.to raise_error(Dzl::NYI)
|
40
|
-
end
|
41
|
-
|
42
32
|
specify 'hash parameters retry their blocks against a hash validator' do
|
43
33
|
HashValidator.any_instance.should_receive(:key).with(:key, {required: true, type: String})
|
44
34
|
class T1 < Dzl::Examples::Base
|
@@ -86,6 +76,38 @@ describe 'hash parameters' do
|
|
86
76
|
end
|
87
77
|
end
|
88
78
|
|
79
|
+
context 'importing & reopening' do
|
80
|
+
specify 'works, basically' do
|
81
|
+
boring_sandwich = {
|
82
|
+
bread: 'multi-grain',
|
83
|
+
meat: 'roast beast',
|
84
|
+
cheese: 'swiss'
|
85
|
+
}
|
86
|
+
|
87
|
+
awesome_sandwich = {
|
88
|
+
bread: 'sourdough',
|
89
|
+
meat: ['roast beast', 'salami', 'turkey'],
|
90
|
+
cheese: 'cheddar'
|
91
|
+
}
|
92
|
+
|
93
|
+
post('/boring_sandwich', {ingredients: boring_sandwich.to_json}) do |response|
|
94
|
+
response.status.should == 200
|
95
|
+
end
|
96
|
+
|
97
|
+
post('/awesome_sandwich', {ingredients: awesome_sandwich.to_json}) do |response|
|
98
|
+
response.status.should == 200
|
99
|
+
end
|
100
|
+
|
101
|
+
post('/another_boring_sandwich', {ingredients: awesome_sandwich.to_json}) do |response|
|
102
|
+
response.status.should == 404
|
103
|
+
end
|
104
|
+
|
105
|
+
post('/another_boring_sandwich', {ingredients: boring_sandwich.to_json}) do |response|
|
106
|
+
response.status.should == 200
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
89
111
|
context 'validate' do
|
90
112
|
specify 'JSON post bodies' do
|
91
113
|
header 'Content-Type', 'application/json'
|
@@ -153,4 +175,17 @@ describe 'hash parameters' do
|
|
153
175
|
get('/mixed', valid).status.should == 200
|
154
176
|
end
|
155
177
|
end
|
178
|
+
|
179
|
+
context "eli's bug" do
|
180
|
+
specify 'omitted optional hashes are OK' do
|
181
|
+
post('/elis_bug', {hash: {id: 1}.to_json}) do |response|
|
182
|
+
response.status.should == 200
|
183
|
+
end
|
184
|
+
|
185
|
+
post('/elis_bug') do |response|
|
186
|
+
JSON.parse(response.body)['errors'].should == nil
|
187
|
+
response.status.should == 200
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
156
191
|
end
|
@@ -172,6 +172,23 @@ describe Dzl::Examples::FunWithParams do
|
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
175
|
+
describe '/api' do
|
176
|
+
it 'should 401 if no api key provided' do
|
177
|
+
get '/api'
|
178
|
+
last_response.status.should == 401
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should 401 if invalid api key provided' do
|
182
|
+
get '/api', {}, {"HTTP_X_API_KEY" => 'invalid-key'}
|
183
|
+
last_response.status.should == 401
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should accept valid api key' do
|
187
|
+
get '/api', {}, {"HTTP_X_API_KEY" => 'valid-key'}
|
188
|
+
last_response.status.should == 200
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
175
192
|
describe '/arithmetic' do
|
176
193
|
it 'should not allow :int < 5' do
|
177
194
|
get('/arithmetic', {int: 4}) do |response|
|
data/spec/hash_validator_spec.rb
CHANGED
@@ -280,4 +280,38 @@ describe HashValidator do
|
|
280
280
|
v.valid?({foo: 'three'}).should == true
|
281
281
|
end
|
282
282
|
end
|
283
|
+
|
284
|
+
context 'cloning' do
|
285
|
+
before(:each) do
|
286
|
+
@original = HashValidator.new do
|
287
|
+
required(:foo)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
specify 'dupes the data in @template' do
|
292
|
+
orig_template = @original.instance_variable_get(:@template)
|
293
|
+
|
294
|
+
cloned = @original.clone
|
295
|
+
|
296
|
+
@original.instance_variable_get(:@template).object_id.should == orig_template.object_id
|
297
|
+
@original.instance_variable_get(:@template).object_id.should_not == cloned.instance_variable_get(:@template).object_id
|
298
|
+
@original.instance_variable_get(:@template).should == cloned.instance_variable_get(:@template)
|
299
|
+
|
300
|
+
cloned.required(:foo) { type Fixnum }
|
301
|
+
|
302
|
+
@original.instance_variable_get(:@template).should_not == cloned.instance_variable_get(:@template)
|
303
|
+
@original.instance_variable_get(:@template)[:keys][:foo][:opts][:type].should == String
|
304
|
+
cloned.instance_variable_get(:@template)[:keys][:foo][:opts][:type].should == Fixnum
|
305
|
+
end
|
306
|
+
|
307
|
+
specify 'validation works as expected' do
|
308
|
+
cloned = @original.clone
|
309
|
+
cloned.required(:foo) { type Fixnum }
|
310
|
+
|
311
|
+
@original.valid?({foo: 'hello'}).should == true
|
312
|
+
@original.valid?({foo: 1}).should == false
|
313
|
+
cloned.valid?({foo: 'hello'}).should == false
|
314
|
+
cloned.valid?({foo: 1}).should == true
|
315
|
+
end
|
316
|
+
end
|
283
317
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dzl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc10
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -14,7 +14,7 @@ date: 2012-05-02 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
17
|
-
requirement: &
|
17
|
+
requirement: &70114952344160 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 1.4.1
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70114952344160
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: activesupport
|
28
|
-
requirement: &
|
28
|
+
requirement: &70114952343660 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
version: 3.2.2
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70114952343660
|
37
37
|
description: Small, fast racktivesupport web framework with handy DSL and explicit
|
38
38
|
parameter validation.
|
39
39
|
email:
|