fragrant 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +22 -0
- data/README.md +6 -0
- data/bin/fragrant +14 -0
- data/lib/fragrant/address_manager.rb +80 -0
- data/lib/fragrant/vagrantfile_generator.rb +64 -0
- data/lib/fragrant.rb +323 -0
- data/spec/fragrant/environments_spec.rb +18 -0
- data/spec/fragrant/vagrantfile_generator_spec.rb +40 -0
- data/spec/fragrant/vms_spec.rb +26 -0
- data/spec/spec_helper.rb +15 -0
- data/vendor/grape/LICENSE +20 -0
- data/vendor/grape/lib/grape/api.rb +420 -0
- data/vendor/grape/lib/grape/cookies.rb +41 -0
- data/vendor/grape/lib/grape/endpoint.rb +377 -0
- data/vendor/grape/lib/grape/entity.rb +378 -0
- data/vendor/grape/lib/grape/exceptions/base.rb +17 -0
- data/vendor/grape/lib/grape/exceptions/validation_error.rb +10 -0
- data/vendor/grape/lib/grape/middleware/auth/basic.rb +30 -0
- data/vendor/grape/lib/grape/middleware/auth/digest.rb +30 -0
- data/vendor/grape/lib/grape/middleware/auth/oauth2.rb +72 -0
- data/vendor/grape/lib/grape/middleware/base.rb +154 -0
- data/vendor/grape/lib/grape/middleware/error.rb +87 -0
- data/vendor/grape/lib/grape/middleware/filter.rb +17 -0
- data/vendor/grape/lib/grape/middleware/formatter.rb +81 -0
- data/vendor/grape/lib/grape/middleware/prefixer.rb +21 -0
- data/vendor/grape/lib/grape/middleware/versioner/header.rb +59 -0
- data/vendor/grape/lib/grape/middleware/versioner/param.rb +44 -0
- data/vendor/grape/lib/grape/middleware/versioner/path.rb +42 -0
- data/vendor/grape/lib/grape/middleware/versioner.rb +29 -0
- data/vendor/grape/lib/grape/route.rb +23 -0
- data/vendor/grape/lib/grape/util/deep_merge.rb +23 -0
- data/vendor/grape/lib/grape/util/hash_stack.rb +100 -0
- data/vendor/grape/lib/grape/validations/coerce.rb +61 -0
- data/vendor/grape/lib/grape/validations/presence.rb +11 -0
- data/vendor/grape/lib/grape/validations/regexp.rb +13 -0
- data/vendor/grape/lib/grape/validations.rb +192 -0
- data/vendor/grape/lib/grape/version.rb +3 -0
- data/vendor/grape/lib/grape.rb +44 -0
- metadata +216 -0
@@ -0,0 +1,378 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
# An Entity is a lightweight structure that allows you to easily
|
5
|
+
# represent data from your application in a consistent and abstracted
|
6
|
+
# way in your API. Entities can also provide documentation for the
|
7
|
+
# fields exposed.
|
8
|
+
#
|
9
|
+
# @example Entity Definition
|
10
|
+
#
|
11
|
+
# module API
|
12
|
+
# module Entities
|
13
|
+
# class User < Grape::Entity
|
14
|
+
# expose :first_name, :last_name, :screen_name, :location
|
15
|
+
# expose :field, :documentation => {:type => "string", :desc => "describe the field"}
|
16
|
+
# expose :latest_status, :using => API::Status, :as => :status, :unless => {:collection => true}
|
17
|
+
# expose :email, :if => {:type => :full}
|
18
|
+
# expose :new_attribute, :if => {:version => 'v2'}
|
19
|
+
# expose(:name){|model,options| [model.first_name, model.last_name].join(' ')}
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Entities are not independent structures, rather, they create
|
25
|
+
# **representations** of other Ruby objects using a number of methods
|
26
|
+
# that are convenient for use in an API. Once you've defined an Entity,
|
27
|
+
# you can use it in your API like this:
|
28
|
+
#
|
29
|
+
# @example Usage in the API Layer
|
30
|
+
#
|
31
|
+
# module API
|
32
|
+
# class Users < Grape::API
|
33
|
+
# version 'v2'
|
34
|
+
#
|
35
|
+
# desc 'User index', { :object_fields => API::Entities::User.documentation }
|
36
|
+
# get '/users' do
|
37
|
+
# @users = User.all
|
38
|
+
# type = current_user.admin? ? :full : :default
|
39
|
+
# present @users, :with => API::Entities::User, :type => type
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
class Entity
|
44
|
+
attr_reader :object, :options
|
45
|
+
|
46
|
+
# The Entity DSL allows you to mix entity functionality into
|
47
|
+
# your existing classes.
|
48
|
+
module DSL
|
49
|
+
def self.included(base)
|
50
|
+
base.extend ClassMethods
|
51
|
+
ancestor_entity_class = base.ancestors.detect{|a| a.entity_class if a.respond_to?(:entity_class)}
|
52
|
+
base.const_set(:Entity, Class.new(ancestor_entity_class || Grape::Entity)) unless const_defined?(:Entity)
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
# Returns the automatically-created entity class for this
|
57
|
+
# Class.
|
58
|
+
def entity_class(search_ancestors=true)
|
59
|
+
klass = const_get(:Entity) if const_defined?(:Entity)
|
60
|
+
klass ||= ancestors.detect{|a| a.entity_class(false) if a.respond_to?(:entity_class) } if search_ancestors
|
61
|
+
klass
|
62
|
+
end
|
63
|
+
|
64
|
+
# Call this to make exposures to the entity for this Class.
|
65
|
+
# Can be called with symbols for the attributes to expose,
|
66
|
+
# a block that yields the full Entity DSL (See Grape::Entity),
|
67
|
+
# or both.
|
68
|
+
#
|
69
|
+
# @example Symbols only.
|
70
|
+
#
|
71
|
+
# class User
|
72
|
+
# include Grape::Entity::DSL
|
73
|
+
#
|
74
|
+
# entity :name, :email
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# @example Mixed.
|
78
|
+
#
|
79
|
+
# class User
|
80
|
+
# include Grape::Entity::DSL
|
81
|
+
#
|
82
|
+
# entity :name, :email do
|
83
|
+
# expose :latest_status, using: Status::Entity, if: :include_status
|
84
|
+
# expose :new_attribute, :if => {:version => 'v2'}
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
def entity(*exposures, &block)
|
88
|
+
entity_class.expose *exposures if exposures.any?
|
89
|
+
entity_class.class_eval(&block) if block_given?
|
90
|
+
entity_class
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Instantiates an entity version of this object.
|
95
|
+
def entity
|
96
|
+
self.class.entity_class.new(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# This method is the primary means by which you will declare what attributes
|
101
|
+
# should be exposed by the entity.
|
102
|
+
#
|
103
|
+
# @option options :as Declare an alias for the representation of this attribute.
|
104
|
+
# @option options :if When passed a Hash, the attribute will only be exposed if the
|
105
|
+
# runtime options match all the conditions passed in. When passed a lambda, the
|
106
|
+
# lambda will execute with two arguments: the object being represented and the
|
107
|
+
# options passed into the representation call. Return true if you want the attribute
|
108
|
+
# to be exposed.
|
109
|
+
# @option options :unless When passed a Hash, the attribute will be exposed if the
|
110
|
+
# runtime options fail to match any of the conditions passed in. If passed a lambda,
|
111
|
+
# it will yield the object being represented and the options passed to the
|
112
|
+
# representation call. Return true to prevent exposure, false to allow it.
|
113
|
+
# @option options :using This option allows you to map an attribute to another Grape
|
114
|
+
# Entity. Pass it a Grape::Entity class and the attribute in question will
|
115
|
+
# automatically be transformed into a representation that will receive the same
|
116
|
+
# options as the parent entity when called. Note that arrays are fine here and
|
117
|
+
# will automatically be detected and handled appropriately.
|
118
|
+
# @option options :proc If you pass a Proc into this option, it will
|
119
|
+
# be used directly to determine the value for that attribute. It
|
120
|
+
# will be called with the represented object as well as the
|
121
|
+
# runtime options that were passed in. You can also just supply a
|
122
|
+
# block to the expose call to achieve the same effect.
|
123
|
+
# @option options :documentation Define documenation for an exposed
|
124
|
+
# field, typically the value is a hash with two fields, type and desc.
|
125
|
+
def self.expose(*args, &block)
|
126
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
127
|
+
|
128
|
+
if args.size > 1
|
129
|
+
raise ArgumentError, "You may not use the :as option on multi-attribute exposures." if options[:as]
|
130
|
+
raise ArgumentError, "You may not use block-setting on multi-attribute exposures." if block_given?
|
131
|
+
end
|
132
|
+
|
133
|
+
raise ArgumentError, "You may not use block-setting when also using format_with" if block_given? && options[:format_with].respond_to?(:call)
|
134
|
+
|
135
|
+
options[:proc] = block if block_given?
|
136
|
+
|
137
|
+
args.each do |attribute|
|
138
|
+
exposures[attribute.to_sym] = options
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a hash of exposures that have been declared for this Entity or ancestors. The keys
|
143
|
+
# are symbolized references to methods on the containing object, the values are
|
144
|
+
# the options that were passed into expose.
|
145
|
+
def self.exposures
|
146
|
+
@exposures ||= {}
|
147
|
+
|
148
|
+
if superclass.respond_to? :exposures
|
149
|
+
@exposures = superclass.exposures.merge(@exposures)
|
150
|
+
end
|
151
|
+
|
152
|
+
@exposures
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns a hash, the keys are symbolized references to fields in the entity,
|
156
|
+
# the values are document keys in the entity's documentation key. When calling
|
157
|
+
# #docmentation, any exposure without a documentation key will be ignored.
|
158
|
+
def self.documentation
|
159
|
+
@documentation ||= exposures.inject({}) do |memo, value|
|
160
|
+
unless value[1][:documentation].nil? || value[1][:documentation].empty?
|
161
|
+
memo[value[0]] = value[1][:documentation]
|
162
|
+
end
|
163
|
+
memo
|
164
|
+
end
|
165
|
+
|
166
|
+
if superclass.respond_to? :documentation
|
167
|
+
@documentation = superclass.documentation.merge(@documentation)
|
168
|
+
end
|
169
|
+
|
170
|
+
@documentation
|
171
|
+
end
|
172
|
+
|
173
|
+
# This allows you to declare a Proc in which exposures can be formatted with.
|
174
|
+
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
|
175
|
+
#
|
176
|
+
# @param name [Symbol] the name of the formatter
|
177
|
+
# @param block [Proc] the block that will interpret the exposed attribute
|
178
|
+
#
|
179
|
+
#
|
180
|
+
#
|
181
|
+
# @example Formatter declaration
|
182
|
+
#
|
183
|
+
# module API
|
184
|
+
# module Entities
|
185
|
+
# class User < Grape::Entity
|
186
|
+
# format_with :timestamp do |date|
|
187
|
+
# date.strftime('%m/%d/%Y')
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# expose :birthday, :last_signed_in, :format_with => :timestamp
|
191
|
+
# end
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# @example Formatters are available to all decendants
|
196
|
+
#
|
197
|
+
# Grape::Entity.format_with :timestamp do |date|
|
198
|
+
# date.strftime('%m/%d/%Y')
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
def self.format_with(name, &block)
|
202
|
+
raise ArgumentError, "You must pass a block for formatters" unless block_given?
|
203
|
+
formatters[name.to_sym] = block
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns a hash of all formatters that are registered for this and it's ancestors.
|
207
|
+
def self.formatters
|
208
|
+
@formatters ||= {}
|
209
|
+
|
210
|
+
if superclass.respond_to? :formatters
|
211
|
+
@formatters = superclass.formatters.merge(@formatters)
|
212
|
+
end
|
213
|
+
|
214
|
+
@formatters
|
215
|
+
end
|
216
|
+
|
217
|
+
# This allows you to set a root element name for your representation.
|
218
|
+
#
|
219
|
+
# @param plural [String] the root key to use when representing
|
220
|
+
# a collection of objects. If missing or nil, no root key will be used
|
221
|
+
# when representing collections of objects.
|
222
|
+
# @param singular [String] the root key to use when representing
|
223
|
+
# a single object. If missing or nil, no root key will be used when
|
224
|
+
# representing an individual object.
|
225
|
+
#
|
226
|
+
# @example Entity Definition
|
227
|
+
#
|
228
|
+
# module API
|
229
|
+
# module Entities
|
230
|
+
# class User < Grape::Entity
|
231
|
+
# root 'users', 'user'
|
232
|
+
# expose :id
|
233
|
+
# end
|
234
|
+
# end
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# @example Usage in the API Layer
|
238
|
+
#
|
239
|
+
# module API
|
240
|
+
# class Users < Grape::API
|
241
|
+
# version 'v2'
|
242
|
+
#
|
243
|
+
# # this will render { "users": [ {"id":"1"}, {"id":"2"} ] }
|
244
|
+
# get '/users' do
|
245
|
+
# @users = User.all
|
246
|
+
# present @users, :with => API::Entities::User
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# # this will render { "user": {"id":"1"} }
|
250
|
+
# get '/users/:id' do
|
251
|
+
# @user = User.find(params[:id])
|
252
|
+
# present @user, :with => API::Entities::User
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
# end
|
256
|
+
def self.root(plural, singular=nil)
|
257
|
+
@collection_root = plural
|
258
|
+
@root = singular
|
259
|
+
end
|
260
|
+
|
261
|
+
# This convenience method allows you to instantiate one or more entities by
|
262
|
+
# passing either a singular or collection of objects. Each object will be
|
263
|
+
# initialized with the same options. If an array of objects is passed in,
|
264
|
+
# an array of entities will be returned. If a single object is passed in,
|
265
|
+
# a single entity will be returned.
|
266
|
+
#
|
267
|
+
# @param objects [Object or Array] One or more objects to be represented.
|
268
|
+
# @param options [Hash] Options that will be passed through to each entity
|
269
|
+
# representation.
|
270
|
+
#
|
271
|
+
# @option options :root [String] override the default root name set for the
|
272
|
+
# entity. Pass nil or false to represent the object or objects with no
|
273
|
+
# root name even if one is defined for the entity.
|
274
|
+
def self.represent(objects, options = {})
|
275
|
+
inner = if objects.respond_to?(:to_ary)
|
276
|
+
objects.to_ary().map{|o| self.new(o, {:collection => true}.merge(options))}
|
277
|
+
else
|
278
|
+
self.new(objects, options)
|
279
|
+
end
|
280
|
+
|
281
|
+
root_element = if options.has_key?(:root)
|
282
|
+
options[:root]
|
283
|
+
else
|
284
|
+
objects.respond_to?(:to_ary) ? @collection_root : @root
|
285
|
+
end
|
286
|
+
root_element ? { root_element => inner } : inner
|
287
|
+
end
|
288
|
+
|
289
|
+
def initialize(object, options = {})
|
290
|
+
@object, @options = object, options
|
291
|
+
end
|
292
|
+
|
293
|
+
def exposures
|
294
|
+
self.class.exposures
|
295
|
+
end
|
296
|
+
|
297
|
+
def documentation
|
298
|
+
self.class.documentation
|
299
|
+
end
|
300
|
+
|
301
|
+
def formatters
|
302
|
+
self.class.formatters
|
303
|
+
end
|
304
|
+
|
305
|
+
# The serializable hash is the Entity's primary output. It is the transformed
|
306
|
+
# hash for the given data model and is used as the basis for serialization to
|
307
|
+
# JSON and other formats.
|
308
|
+
#
|
309
|
+
# @param options [Hash] Any options you pass in here will be known to the entity
|
310
|
+
# representation, this is where you can trigger things from conditional options
|
311
|
+
# etc.
|
312
|
+
def serializable_hash(runtime_options = {})
|
313
|
+
return nil if object.nil?
|
314
|
+
opts = options.merge(runtime_options || {})
|
315
|
+
exposures.inject({}) do |output, (attribute, exposure_options)|
|
316
|
+
if (exposure_options.has_key?(:proc) || object.respond_to?(attribute)) && conditions_met?(exposure_options, opts)
|
317
|
+
partial_output = value_for(attribute, opts)
|
318
|
+
output[key_for(attribute)] =
|
319
|
+
if partial_output.respond_to? :serializable_hash
|
320
|
+
partial_output.serializable_hash(runtime_options)
|
321
|
+
elsif partial_output.kind_of?(Array) && !partial_output.map {|o| o.respond_to? :serializable_hash}.include?(false)
|
322
|
+
partial_output.map {|o| o.serializable_hash}
|
323
|
+
else
|
324
|
+
partial_output
|
325
|
+
end
|
326
|
+
end
|
327
|
+
output
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
alias :as_json :serializable_hash
|
332
|
+
|
333
|
+
protected
|
334
|
+
|
335
|
+
def key_for(attribute)
|
336
|
+
exposures[attribute.to_sym][:as] || attribute.to_sym
|
337
|
+
end
|
338
|
+
|
339
|
+
def value_for(attribute, options = {})
|
340
|
+
exposure_options = exposures[attribute.to_sym]
|
341
|
+
|
342
|
+
if exposure_options[:proc]
|
343
|
+
exposure_options[:proc].call(object, options)
|
344
|
+
elsif exposure_options[:using]
|
345
|
+
exposure_options[:using].represent(object.send(attribute), :root => nil)
|
346
|
+
elsif exposure_options[:format_with]
|
347
|
+
format_with = exposure_options[:format_with]
|
348
|
+
|
349
|
+
if format_with.is_a?(Symbol) && formatters[format_with]
|
350
|
+
formatters[format_with].call(object.send(attribute))
|
351
|
+
elsif format_with.is_a?(Symbol)
|
352
|
+
self.send(format_with, object.send(attribute))
|
353
|
+
elsif format_with.respond_to? :call
|
354
|
+
format_with.call(object.send(attribute))
|
355
|
+
end
|
356
|
+
else
|
357
|
+
object.send(attribute)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def conditions_met?(exposure_options, options)
|
362
|
+
if_condition = exposure_options[:if]
|
363
|
+
unless_condition = exposure_options[:unless]
|
364
|
+
|
365
|
+
case if_condition
|
366
|
+
when Hash; if_condition.each_pair{|k,v| return false if options[k.to_sym] != v }
|
367
|
+
when Proc; return false unless if_condition.call(object, options)
|
368
|
+
end
|
369
|
+
|
370
|
+
case unless_condition
|
371
|
+
when Hash; unless_condition.each_pair{|k,v| return false if options[k.to_sym] == v}
|
372
|
+
when Proc; return false if unless_condition.call(object, options)
|
373
|
+
end
|
374
|
+
|
375
|
+
true
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module Exceptions
|
3
|
+
class Base < StandardError
|
4
|
+
attr_reader :status, :message, :headers
|
5
|
+
|
6
|
+
def initialize(args = {})
|
7
|
+
@status = args[:status] || nil
|
8
|
+
@message = args[:message] || nil
|
9
|
+
@headers = args[:headers] || nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](index)
|
13
|
+
self.send(index)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/auth/basic'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Auth
|
6
|
+
class Basic < Grape::Middleware::Base
|
7
|
+
attr_reader :authenticator
|
8
|
+
|
9
|
+
def initialize(app, options = {}, &authenticator)
|
10
|
+
super(app, options)
|
11
|
+
@authenticator = authenticator
|
12
|
+
end
|
13
|
+
|
14
|
+
def basic_request
|
15
|
+
Rack::Auth::Basic::Request.new(env)
|
16
|
+
end
|
17
|
+
|
18
|
+
def credentials
|
19
|
+
basic_request.provided?? basic_request.credentials : [nil, nil]
|
20
|
+
end
|
21
|
+
|
22
|
+
def before
|
23
|
+
unless authenticator.call(*credentials)
|
24
|
+
throw :error, :status => 401, :message => "API Authorization Failed."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/auth/digest/md5'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Auth
|
6
|
+
class Digest < Grape::Middleware::Base
|
7
|
+
attr_reader :authenticator
|
8
|
+
|
9
|
+
def initialize(app, options = {}, &authenticator)
|
10
|
+
super(app, options)
|
11
|
+
@authenticator = authenticator
|
12
|
+
end
|
13
|
+
|
14
|
+
def digest_request
|
15
|
+
Rack::Auth::Digest::Request.new(env)
|
16
|
+
end
|
17
|
+
|
18
|
+
def credentials
|
19
|
+
digest_request.provided?? digest_request.credentials : [nil, nil]
|
20
|
+
end
|
21
|
+
|
22
|
+
def before
|
23
|
+
unless authenticator.call(*credentials)
|
24
|
+
throw :error, :status => 401, :message => "API Authorization Failed."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Grape::Middleware::Auth
|
2
|
+
# OAuth 2.0 authorization for Grape APIs.
|
3
|
+
class OAuth2 < Grape::Middleware::Base
|
4
|
+
def default_options
|
5
|
+
{
|
6
|
+
:token_class => 'AccessToken',
|
7
|
+
:realm => 'OAuth API',
|
8
|
+
:parameter => %w(bearer_token oauth_token),
|
9
|
+
:accepted_headers => %w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION),
|
10
|
+
:header => [/Bearer (.*)/i, /OAuth (.*)/i]
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def before
|
15
|
+
verify_token(token_parameter || token_header)
|
16
|
+
end
|
17
|
+
|
18
|
+
def token_parameter
|
19
|
+
Array(options[:parameter]).each do |p|
|
20
|
+
return request[p] if request[p]
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def token_header
|
26
|
+
return false unless authorization_header
|
27
|
+
Array(options[:header]).each do |regexp|
|
28
|
+
if authorization_header =~ regexp
|
29
|
+
return $1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def authorization_header
|
36
|
+
options[:accepted_headers].each do |head|
|
37
|
+
return env[head] if env[head]
|
38
|
+
end
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def token_class
|
43
|
+
@klass ||= eval(options[:token_class])
|
44
|
+
end
|
45
|
+
|
46
|
+
def verify_token(token)
|
47
|
+
if token = token_class.verify(token)
|
48
|
+
if token.respond_to?(:expired?) && token.expired?
|
49
|
+
error_out(401, 'expired_token')
|
50
|
+
else
|
51
|
+
if !token.respond_to?(:permission_for?) || token.permission_for?(env)
|
52
|
+
env['api.token'] = token
|
53
|
+
else
|
54
|
+
error_out(403, 'insufficient_scope')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
error_out(401, 'invalid_token')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def error_out(status, error)
|
63
|
+
throw :error,
|
64
|
+
:message => error,
|
65
|
+
:status => status,
|
66
|
+
:headers => {
|
67
|
+
'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'multi_xml'
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Middleware
|
6
|
+
class Base
|
7
|
+
attr_reader :app, :env, :options
|
8
|
+
|
9
|
+
# @param [Rack Application] app The standard argument for a Rack middleware.
|
10
|
+
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
11
|
+
def initialize(app, options = {})
|
12
|
+
@app = app
|
13
|
+
@options = default_options.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_options; {} end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
dup.call!(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call!(env)
|
23
|
+
@env = env
|
24
|
+
before
|
25
|
+
@app_response = @app.call(@env)
|
26
|
+
after || @app_response
|
27
|
+
end
|
28
|
+
|
29
|
+
# @abstract
|
30
|
+
# Called before the application is called in the middleware lifecycle.
|
31
|
+
def before; end
|
32
|
+
# @abstract
|
33
|
+
# Called after the application is called in the middleware lifecycle.
|
34
|
+
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
|
35
|
+
def after; end
|
36
|
+
|
37
|
+
def request
|
38
|
+
Rack::Request.new(self.env)
|
39
|
+
end
|
40
|
+
|
41
|
+
def response
|
42
|
+
Rack::Response.new(@app_response)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
module Formats
|
47
|
+
|
48
|
+
CONTENT_TYPES = {
|
49
|
+
:xml => 'application/xml',
|
50
|
+
:json => 'application/json',
|
51
|
+
:atom => 'application/atom+xml',
|
52
|
+
:rss => 'application/rss+xml',
|
53
|
+
:txt => 'text/plain'
|
54
|
+
}
|
55
|
+
FORMATTERS = {
|
56
|
+
:json => :encode_json,
|
57
|
+
:txt => :encode_txt,
|
58
|
+
:xml => :encode_xml
|
59
|
+
}
|
60
|
+
PARSERS = {
|
61
|
+
:json => :decode_json,
|
62
|
+
:xml => :decode_xml
|
63
|
+
}
|
64
|
+
|
65
|
+
def formatters
|
66
|
+
FORMATTERS.merge(options[:formatters] || {})
|
67
|
+
end
|
68
|
+
|
69
|
+
def parsers
|
70
|
+
PARSERS.merge(options[:parsers] || {})
|
71
|
+
end
|
72
|
+
|
73
|
+
def content_types
|
74
|
+
CONTENT_TYPES.merge(options[:content_types] || {})
|
75
|
+
end
|
76
|
+
|
77
|
+
def content_type
|
78
|
+
content_types[options[:format]] || 'text/html'
|
79
|
+
end
|
80
|
+
|
81
|
+
def mime_types
|
82
|
+
content_types.invert
|
83
|
+
end
|
84
|
+
|
85
|
+
def formatter_for(api_format)
|
86
|
+
spec = formatters[api_format]
|
87
|
+
case spec
|
88
|
+
when nil
|
89
|
+
lambda { |obj| obj }
|
90
|
+
when Symbol
|
91
|
+
method(spec)
|
92
|
+
else
|
93
|
+
spec
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def parser_for(api_format)
|
98
|
+
spec = parsers[api_format]
|
99
|
+
case spec
|
100
|
+
when nil
|
101
|
+
nil
|
102
|
+
when Symbol
|
103
|
+
method(spec)
|
104
|
+
else
|
105
|
+
spec
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def decode_json(object)
|
110
|
+
MultiJson.load(object)
|
111
|
+
end
|
112
|
+
|
113
|
+
def serializable?(object)
|
114
|
+
object.respond_to?(:serializable_hash) ||
|
115
|
+
object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false) ||
|
116
|
+
object.kind_of?(Hash)
|
117
|
+
end
|
118
|
+
|
119
|
+
def serialize(object)
|
120
|
+
if object.respond_to? :serializable_hash
|
121
|
+
object.serializable_hash
|
122
|
+
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
|
123
|
+
object.map {|o| o.serializable_hash }
|
124
|
+
elsif object.kind_of?(Hash)
|
125
|
+
object.inject({}) { |h,(k,v)| h[k] = serialize(v); h }
|
126
|
+
else
|
127
|
+
object
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def encode_json(object)
|
132
|
+
return object if object.is_a?(String)
|
133
|
+
return MultiJson.dump(serialize(object)) if serializable?(object)
|
134
|
+
return object.to_json if object.respond_to?(:to_json)
|
135
|
+
|
136
|
+
MultiJson.dump(object)
|
137
|
+
end
|
138
|
+
|
139
|
+
def encode_txt(object)
|
140
|
+
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
|
141
|
+
end
|
142
|
+
|
143
|
+
def decode_xml(object)
|
144
|
+
MultiXml.parse(object)
|
145
|
+
end
|
146
|
+
|
147
|
+
def encode_xml(object)
|
148
|
+
object.respond_to?(:to_xml) ? object.to_xml : object.to_s
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|