grape-security 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +70 -0
  5. data/.travis.yml +18 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +314 -0
  8. data/CONTRIBUTING.md +118 -0
  9. data/Gemfile +21 -0
  10. data/Guardfile +14 -0
  11. data/LICENSE +20 -0
  12. data/README.md +1777 -0
  13. data/RELEASING.md +105 -0
  14. data/Rakefile +69 -0
  15. data/UPGRADING.md +124 -0
  16. data/grape-security.gemspec +39 -0
  17. data/grape.png +0 -0
  18. data/lib/grape.rb +99 -0
  19. data/lib/grape/api.rb +646 -0
  20. data/lib/grape/cookies.rb +39 -0
  21. data/lib/grape/endpoint.rb +533 -0
  22. data/lib/grape/error_formatter/base.rb +31 -0
  23. data/lib/grape/error_formatter/json.rb +15 -0
  24. data/lib/grape/error_formatter/txt.rb +16 -0
  25. data/lib/grape/error_formatter/xml.rb +15 -0
  26. data/lib/grape/exceptions/base.rb +66 -0
  27. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  28. data/lib/grape/exceptions/invalid_formatter.rb +10 -0
  29. data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
  30. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
  31. data/lib/grape/exceptions/missing_mime_type.rb +10 -0
  32. data/lib/grape/exceptions/missing_option.rb +10 -0
  33. data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
  34. data/lib/grape/exceptions/unknown_options.rb +10 -0
  35. data/lib/grape/exceptions/unknown_validator.rb +10 -0
  36. data/lib/grape/exceptions/validation.rb +26 -0
  37. data/lib/grape/exceptions/validation_errors.rb +43 -0
  38. data/lib/grape/formatter/base.rb +31 -0
  39. data/lib/grape/formatter/json.rb +12 -0
  40. data/lib/grape/formatter/serializable_hash.rb +35 -0
  41. data/lib/grape/formatter/txt.rb +11 -0
  42. data/lib/grape/formatter/xml.rb +12 -0
  43. data/lib/grape/http/request.rb +26 -0
  44. data/lib/grape/locale/en.yml +32 -0
  45. data/lib/grape/middleware/auth/base.rb +30 -0
  46. data/lib/grape/middleware/auth/basic.rb +13 -0
  47. data/lib/grape/middleware/auth/digest.rb +13 -0
  48. data/lib/grape/middleware/auth/oauth2.rb +83 -0
  49. data/lib/grape/middleware/base.rb +62 -0
  50. data/lib/grape/middleware/error.rb +89 -0
  51. data/lib/grape/middleware/filter.rb +17 -0
  52. data/lib/grape/middleware/formatter.rb +150 -0
  53. data/lib/grape/middleware/globals.rb +13 -0
  54. data/lib/grape/middleware/versioner.rb +32 -0
  55. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  56. data/lib/grape/middleware/versioner/header.rb +132 -0
  57. data/lib/grape/middleware/versioner/param.rb +42 -0
  58. data/lib/grape/middleware/versioner/path.rb +52 -0
  59. data/lib/grape/namespace.rb +23 -0
  60. data/lib/grape/parser/base.rb +29 -0
  61. data/lib/grape/parser/json.rb +11 -0
  62. data/lib/grape/parser/xml.rb +11 -0
  63. data/lib/grape/path.rb +70 -0
  64. data/lib/grape/route.rb +27 -0
  65. data/lib/grape/util/content_types.rb +18 -0
  66. data/lib/grape/util/deep_merge.rb +23 -0
  67. data/lib/grape/util/hash_stack.rb +120 -0
  68. data/lib/grape/validations.rb +322 -0
  69. data/lib/grape/validations/coerce.rb +63 -0
  70. data/lib/grape/validations/default.rb +25 -0
  71. data/lib/grape/validations/exactly_one_of.rb +26 -0
  72. data/lib/grape/validations/mutual_exclusion.rb +25 -0
  73. data/lib/grape/validations/presence.rb +16 -0
  74. data/lib/grape/validations/regexp.rb +12 -0
  75. data/lib/grape/validations/values.rb +23 -0
  76. data/lib/grape/version.rb +3 -0
  77. data/spec/grape/api_spec.rb +2571 -0
  78. data/spec/grape/endpoint_spec.rb +784 -0
  79. data/spec/grape/entity_spec.rb +324 -0
  80. data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
  81. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
  82. data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
  83. data/spec/grape/exceptions/missing_option_spec.rb +18 -0
  84. data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
  85. data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
  86. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  87. data/spec/grape/middleware/auth/basic_spec.rb +31 -0
  88. data/spec/grape/middleware/auth/digest_spec.rb +47 -0
  89. data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
  90. data/spec/grape/middleware/base_spec.rb +58 -0
  91. data/spec/grape/middleware/error_spec.rb +45 -0
  92. data/spec/grape/middleware/exception_spec.rb +184 -0
  93. data/spec/grape/middleware/formatter_spec.rb +258 -0
  94. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  95. data/spec/grape/middleware/versioner/header_spec.rb +302 -0
  96. data/spec/grape/middleware/versioner/param_spec.rb +58 -0
  97. data/spec/grape/middleware/versioner/path_spec.rb +44 -0
  98. data/spec/grape/middleware/versioner_spec.rb +22 -0
  99. data/spec/grape/path_spec.rb +229 -0
  100. data/spec/grape/util/hash_stack_spec.rb +132 -0
  101. data/spec/grape/validations/coerce_spec.rb +208 -0
  102. data/spec/grape/validations/default_spec.rb +123 -0
  103. data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
  104. data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
  105. data/spec/grape/validations/presence_spec.rb +142 -0
  106. data/spec/grape/validations/regexp_spec.rb +40 -0
  107. data/spec/grape/validations/values_spec.rb +152 -0
  108. data/spec/grape/validations/zh-CN.yml +10 -0
  109. data/spec/grape/validations_spec.rb +994 -0
  110. data/spec/shared/versioning_examples.rb +121 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/basic_auth_encode_helpers.rb +3 -0
  113. data/spec/support/content_type_helpers.rb +11 -0
  114. data/spec/support/versioned_helpers.rb +50 -0
  115. metadata +421 -0
@@ -0,0 +1,324 @@
1
+ require 'spec_helper'
2
+ require 'grape_entity'
3
+
4
+ describe Grape::Entity do
5
+ subject { Class.new(Grape::API) }
6
+
7
+ def app
8
+ subject
9
+ end
10
+
11
+ describe '#present' do
12
+ it 'sets the object as the body if no options are provided' do
13
+ subject.get '/example' do
14
+ present(abc: 'def')
15
+ body.should == { abc: 'def' }
16
+ end
17
+ get '/example'
18
+ end
19
+
20
+ it 'calls through to the provided entity class if one is given' do
21
+ subject.get '/example' do
22
+ entity_mock = Object.new
23
+ entity_mock.should_receive(:represent)
24
+ present Object.new, with: entity_mock
25
+ end
26
+ get '/example'
27
+ end
28
+
29
+ it 'pulls a representation from the class options if it exists' do
30
+ entity = Class.new(Grape::Entity)
31
+ allow(entity).to receive(:represent).and_return("Hiya")
32
+
33
+ subject.represent Object, with: entity
34
+ subject.get '/example' do
35
+ present Object.new
36
+ end
37
+ get '/example'
38
+ expect(last_response.body).to eq('Hiya')
39
+ end
40
+
41
+ it 'pulls a representation from the class options if the presented object is a collection of objects' do
42
+ entity = Class.new(Grape::Entity)
43
+ allow(entity).to receive(:represent).and_return("Hiya")
44
+
45
+ class TestObject
46
+ end
47
+
48
+ class FakeCollection
49
+ def first
50
+ TestObject.new
51
+ end
52
+ end
53
+
54
+ subject.represent TestObject, with: entity
55
+ subject.get '/example' do
56
+ present [TestObject.new]
57
+ end
58
+
59
+ subject.get '/example2' do
60
+ present FakeCollection.new
61
+ end
62
+
63
+ get '/example'
64
+ expect(last_response.body).to eq("Hiya")
65
+
66
+ get '/example2'
67
+ expect(last_response.body).to eq("Hiya")
68
+ end
69
+
70
+ it 'pulls a representation from the class ancestor if it exists' do
71
+ entity = Class.new(Grape::Entity)
72
+ allow(entity).to receive(:represent).and_return("Hiya")
73
+
74
+ subclass = Class.new(Object)
75
+
76
+ subject.represent Object, with: entity
77
+ subject.get '/example' do
78
+ present subclass.new
79
+ end
80
+ get '/example'
81
+ expect(last_response.body).to eq('Hiya')
82
+ end
83
+
84
+ it 'automatically uses Klass::Entity if that exists' do
85
+ some_model = Class.new
86
+ entity = Class.new(Grape::Entity)
87
+ allow(entity).to receive(:represent).and_return("Auto-detect!")
88
+
89
+ some_model.const_set :Entity, entity
90
+
91
+ subject.get '/example' do
92
+ present some_model.new
93
+ end
94
+ get '/example'
95
+ expect(last_response.body).to eq('Auto-detect!')
96
+ end
97
+
98
+ it 'automatically uses Klass::Entity based on the first object in the collection being presented' do
99
+ some_model = Class.new
100
+ entity = Class.new(Grape::Entity)
101
+ allow(entity).to receive(:represent).and_return("Auto-detect!")
102
+
103
+ some_model.const_set :Entity, entity
104
+
105
+ subject.get '/example' do
106
+ present [some_model.new]
107
+ end
108
+ get '/example'
109
+ expect(last_response.body).to eq('Auto-detect!')
110
+ end
111
+
112
+ it 'does not run autodetection for Entity when explicitely provided' do
113
+ entity = Class.new(Grape::Entity)
114
+ some_array = Array.new
115
+
116
+ subject.get '/example' do
117
+ present some_array, with: entity
118
+ end
119
+
120
+ expect(some_array).not_to receive(:first)
121
+ get '/example'
122
+ end
123
+
124
+ it 'does not use #first method on ActiveRecord::Relation to prevent needless sql query' do
125
+ entity = Class.new(Grape::Entity)
126
+ some_relation = Class.new
127
+ some_model = Class.new
128
+
129
+ allow(entity).to receive(:represent).and_return("Auto-detect!")
130
+ allow(some_relation).to receive(:first)
131
+ allow(some_relation).to receive(:klass).and_return(some_model)
132
+
133
+ some_model.const_set :Entity, entity
134
+
135
+ subject.get '/example' do
136
+ present some_relation
137
+ end
138
+
139
+ expect(some_relation).not_to receive(:first)
140
+ get '/example'
141
+ expect(last_response.body).to eq('Auto-detect!')
142
+ end
143
+
144
+ it 'autodetection does not use Entity if it is not a presenter' do
145
+ some_model = Class.new
146
+ entity = Class.new
147
+
148
+ some_model.class.const_set :Entity, entity
149
+
150
+ subject.get '/example' do
151
+ present some_model
152
+ end
153
+ get '/example'
154
+ expect(entity).not_to receive(:represent)
155
+ end
156
+
157
+ it 'adds a root key to the output if one is given' do
158
+ subject.get '/example' do
159
+ present({ abc: 'def' }, root: :root)
160
+ body.should == { root: { abc: 'def' } }
161
+ end
162
+ get '/example'
163
+ end
164
+
165
+ [:json, :serializable_hash].each do |format|
166
+
167
+ it 'presents with #{format}' do
168
+ entity = Class.new(Grape::Entity)
169
+ entity.root "examples", "example"
170
+ entity.expose :id
171
+
172
+ subject.format format
173
+ subject.get '/example' do
174
+ c = Class.new do
175
+ attr_reader :id
176
+ def initialize(id)
177
+ @id = id
178
+ end
179
+ end
180
+ present c.new(1), with: entity
181
+ end
182
+
183
+ get '/example'
184
+ expect(last_response.status).to eq(200)
185
+ expect(last_response.body).to eq('{"example":{"id":1}}')
186
+ end
187
+
188
+ it 'presents with #{format} collection' do
189
+ entity = Class.new(Grape::Entity)
190
+ entity.root "examples", "example"
191
+ entity.expose :id
192
+
193
+ subject.format format
194
+ subject.get '/examples' do
195
+ c = Class.new do
196
+ attr_reader :id
197
+ def initialize(id)
198
+ @id = id
199
+ end
200
+ end
201
+ examples = [c.new(1), c.new(2)]
202
+ present examples, with: entity
203
+ end
204
+
205
+ get '/examples'
206
+ expect(last_response.status).to eq(200)
207
+ expect(last_response.body).to eq('{"examples":[{"id":1},{"id":2}]}')
208
+ end
209
+
210
+ end
211
+
212
+ it 'presents with xml' do
213
+ entity = Class.new(Grape::Entity)
214
+ entity.root "examples", "example"
215
+ entity.expose :name
216
+
217
+ subject.format :xml
218
+
219
+ subject.get '/example' do
220
+ c = Class.new do
221
+ attr_reader :name
222
+ def initialize(args)
223
+ @name = args[:name] || "no name set"
224
+ end
225
+ end
226
+ present c.new(name: "johnnyiller"), with: entity
227
+ end
228
+ get '/example'
229
+ expect(last_response.status).to eq(200)
230
+ expect(last_response.headers['Content-type']).to eq("application/xml")
231
+ expect(last_response.body).to eq <<-XML
232
+ <?xml version="1.0" encoding="UTF-8"?>
233
+ <hash>
234
+ <example>
235
+ <name>johnnyiller</name>
236
+ </example>
237
+ </hash>
238
+ XML
239
+ end
240
+
241
+ it 'presents with json' do
242
+ entity = Class.new(Grape::Entity)
243
+ entity.root "examples", "example"
244
+ entity.expose :name
245
+
246
+ subject.format :json
247
+
248
+ subject.get '/example' do
249
+ c = Class.new do
250
+ attr_reader :name
251
+ def initialize(args)
252
+ @name = args[:name] || "no name set"
253
+ end
254
+ end
255
+ present c.new(name: "johnnyiller"), with: entity
256
+ end
257
+ get '/example'
258
+ expect(last_response.status).to eq(200)
259
+ expect(last_response.headers['Content-type']).to eq("application/json")
260
+ expect(last_response.body).to eq('{"example":{"name":"johnnyiller"}}')
261
+ end
262
+
263
+ it 'presents with jsonp utilising Rack::JSONP' do
264
+ require 'rack/contrib'
265
+
266
+ # Include JSONP middleware
267
+ subject.use Rack::JSONP
268
+
269
+ entity = Class.new(Grape::Entity)
270
+ entity.root "examples", "example"
271
+ entity.expose :name
272
+
273
+ # Rack::JSONP expects a standard JSON response
274
+ subject.format :json
275
+
276
+ subject.get '/example' do
277
+ c = Class.new do
278
+ attr_reader :name
279
+ def initialize(args)
280
+ @name = args[:name] || "no name set"
281
+ end
282
+ end
283
+
284
+ present c.new(name: "johnnyiller"), with: entity
285
+ end
286
+
287
+ get '/example?callback=abcDef'
288
+ expect(last_response.status).to eq(200)
289
+ expect(last_response.headers['Content-type']).to eq("application/javascript")
290
+ expect(last_response.body).to eq('abcDef({"example":{"name":"johnnyiller"}})')
291
+ end
292
+
293
+ context "present with multiple entities" do
294
+ it "present with multiple entities using optional symbol" do
295
+ user = Class.new do
296
+ attr_reader :name
297
+ def initialize(args)
298
+ @name = args[:name] || "no name set"
299
+ end
300
+ end
301
+ user1 = user.new(name: 'user1')
302
+ user2 = user.new(name: 'user2')
303
+
304
+ entity = Class.new(Grape::Entity)
305
+ entity.expose :name
306
+
307
+ subject.format :json
308
+ subject.get '/example' do
309
+ present :page, 1
310
+ present :user1, user1, with: entity
311
+ present :user2, user2, with: entity
312
+ end
313
+ get '/example'
314
+ expect_response_json = {
315
+ "page" => 1,
316
+ "user1" => { "name" => "user1" },
317
+ "user2" => { "name" => "user2" }
318
+ }
319
+ expect(JSON(last_response.body)).to eq(expect_response_json)
320
+ end
321
+
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::InvalidFormatter do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new(String, 'xml')
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ expect(error.message).to include(
12
+ "cannot convert String to xml"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::InvalidVersionerOption do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new("headers")
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ expect(error.message).to include(
12
+ "Unknown :using for versioner: headers"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Exceptions::MissingMimeType do
4
+ describe "#message" do
5
+
6
+ let(:error) do
7
+ described_class.new("new_json")
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ expect(error.message).to include "missing mime type for new_json"
12
+ end
13
+
14
+ it "contains the resolution in the message" do
15
+ expect(error.message).to include "or add your own with content_type :new_json, 'application/new_json' "
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::MissingOption do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new(:path)
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ expect(error.message).to include(
12
+ "You must specify :path options."
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::UnknownOptions do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new([:a, :b])
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ expect(error.message).to include(
12
+ "unknown options: "
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::UnknownValidator do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new('gt_10')
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ expect(error.message).to include(
12
+ "unknown validator: gt_10"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Grape::Exceptions::ValidationErrors do
5
+ let(:validation_message) { "FooBar is invalid" }
6
+ let(:validation_error) { OpenStruct.new(param: validation_message) }
7
+
8
+ context "message" do
9
+ context "is not repeated" do
10
+ let(:error) do
11
+ described_class.new(errors: [validation_error, validation_error])
12
+ end
13
+ subject(:message) { error.message.split(',').map(&:strip) }
14
+
15
+ it { expect(message).to include validation_message }
16
+ it { expect(message.size).to eq 1 }
17
+ end
18
+ end
19
+ end