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,31 @@
1
+ module Grape
2
+ module ErrorFormatter
3
+ module Base
4
+ class << self
5
+ FORMATTERS = {
6
+ serializable_hash: Grape::ErrorFormatter::Json,
7
+ json: Grape::ErrorFormatter::Json,
8
+ jsonapi: Grape::ErrorFormatter::Json,
9
+ txt: Grape::ErrorFormatter::Txt,
10
+ xml: Grape::ErrorFormatter::Xml
11
+ }
12
+
13
+ def formatters(options)
14
+ FORMATTERS.merge(options[:error_formatters] || {})
15
+ end
16
+
17
+ def formatter_for(api_format, options = {})
18
+ spec = formatters(options)[api_format]
19
+ case spec
20
+ when nil
21
+ options[:default_error_formatter] || Grape::ErrorFormatter::Txt
22
+ when Symbol
23
+ method(spec)
24
+ else
25
+ spec
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Grape
2
+ module ErrorFormatter
3
+ module Json
4
+ class << self
5
+ def call(message, backtrace, options = {}, env = nil)
6
+ result = message.is_a?(Hash) ? message : { error: message }
7
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
8
+ result = result.merge(backtrace: backtrace)
9
+ end
10
+ MultiJson.dump(result)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Grape
2
+ module ErrorFormatter
3
+ module Txt
4
+ class << self
5
+ def call(message, backtrace, options = {}, env = nil)
6
+ result = message.is_a?(Hash) ? MultiJson.dump(message) : message
7
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
8
+ result += "\r\n "
9
+ result += backtrace.join("\r\n ")
10
+ end
11
+ result
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Grape
2
+ module ErrorFormatter
3
+ module Xml
4
+ class << self
5
+ def call(message, backtrace, options = {}, env = nil)
6
+ result = message.is_a?(Hash) ? message : { message: message }
7
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
8
+ result = result.merge(backtrace: backtrace)
9
+ end
10
+ result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,66 @@
1
+ module Grape
2
+ module Exceptions
3
+ class Base < StandardError
4
+ BASE_MESSAGES_KEY = 'grape.errors.messages'
5
+ BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
6
+ FALLBACK_LOCALE = :en
7
+
8
+ attr_reader :status, :message, :headers
9
+
10
+ def initialize(args = {})
11
+ @status = args[:status] || nil
12
+ @message = args[:message] || nil
13
+ @headers = args[:headers] || nil
14
+ end
15
+
16
+ def [](index)
17
+ send index
18
+ end
19
+
20
+ protected
21
+
22
+ # TODO: translate attribute first
23
+ # if BASE_ATTRIBUTES_KEY.key respond to a string message, then short_message is returned
24
+ # if BASE_ATTRIBUTES_KEY.key respond to a Hash, means it may have problem , summary and resolution
25
+ def compose_message(key, attributes = {})
26
+ short_message = translate_message(key, attributes)
27
+ if short_message.is_a? Hash
28
+ @problem = problem(key, attributes)
29
+ @summary = summary(key, attributes)
30
+ @resolution = resolution(key, attributes)
31
+ [["Problem", @problem], ["Summary", @summary], ["Resolution", @resolution]].reduce("") do |message, detail_array|
32
+ message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
33
+ message
34
+ end
35
+ else
36
+ short_message
37
+ end
38
+ end
39
+
40
+ def problem(key, attributes)
41
+ translate_message("#{key}.problem", attributes)
42
+ end
43
+
44
+ def summary(key, attributes)
45
+ translate_message("#{key}.summary", attributes)
46
+ end
47
+
48
+ def resolution(key, attributes)
49
+ translate_message("#{key}.resolution", attributes)
50
+ end
51
+
52
+ def translate_attribute(key, options = {})
53
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
54
+ end
55
+
56
+ def translate_message(key, options = {})
57
+ translate("#{BASE_MESSAGES_KEY}.#{key}", { default: '' }.merge(options))
58
+ end
59
+
60
+ def translate(key, options = {})
61
+ message = ::I18n.translate(key, options)
62
+ message.present? ? message : ::I18n.translate(key, options.merge(locale: FALLBACK_LOCALE))
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class IncompatibleOptionValues < Base
5
+ def initialize(option1, value1, option2, value2)
6
+ super(message: compose_message("incompatible_option_values", option1: option1, value1: value1, option2: option2, value2: value2))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class InvalidFormatter < Base
5
+ def initialize(klass, to_format)
6
+ super(message: compose_message("invalid_formatter", klass: klass, to_format: to_format))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class InvalidVersionerOption < Base
5
+ def initialize(strategy)
6
+ super(message: compose_message("invalid_versioner_option", strategy: strategy))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class InvalidWithOptionForRepresent < Base
5
+ def initialize
6
+ super(message: compose_message("invalid_with_option_for_represent"))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class MissingMimeType < Base
5
+ def initialize(new_format)
6
+ super(message: compose_message("missing_mime_type", new_format: new_format))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class MissingOption < Base
5
+ def initialize(option)
6
+ super(message: compose_message("missing_option", option: option))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class MissingVendorOption < Base
5
+ def initialize
6
+ super(message: compose_message("missing_vendor_option"))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class UnknownOptions < Base
5
+ def initialize(options)
6
+ super(message: compose_message("unknown_options", options: options))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class UnknownValidator < Base
5
+ def initialize(validator_type)
6
+ super(message: compose_message("unknown_validator", validator_type: validator_type))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ require 'grape/exceptions/base'
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class Validation < Grape::Exceptions::Base
6
+ attr_accessor :param
7
+
8
+ def initialize(args = {})
9
+ raise "Param is missing:" unless args.key? :param
10
+ @param = args[:param]
11
+ args[:message] = translate_message(args[:message_key]) if args.key? :message_key
12
+ super
13
+ end
14
+
15
+ # remove all the unnecessary stuff from Grape::Exceptions::Base like status
16
+ # and headers when converting a validation error to json or string
17
+ def as_json(*args)
18
+ to_s
19
+ end
20
+
21
+ def to_s
22
+ message
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ require 'grape/exceptions/base'
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class ValidationErrors < Grape::Exceptions::Base
6
+ include Enumerable
7
+
8
+ attr_reader :errors
9
+
10
+ def initialize(args = {})
11
+ @errors = {}
12
+ args[:errors].each do |validation_error|
13
+ @errors[validation_error.param] ||= []
14
+ @errors[validation_error.param] << validation_error
15
+ end
16
+ super message: full_messages.join(', '), status: 400
17
+ end
18
+
19
+ def each
20
+ errors.each_pair do |attribute, errors|
21
+ errors.each do |error|
22
+ yield attribute, error
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def full_messages
30
+ map { |attribute, error| full_message(attribute, error) }.uniq
31
+ end
32
+
33
+ def full_message(attribute, error)
34
+ I18n.t(
35
+ "grape.errors.format".to_sym,
36
+ default: "%{attribute} %{message}",
37
+ attribute: translate_attribute(attribute),
38
+ message: error.message
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module Grape
2
+ module Formatter
3
+ module Base
4
+ class << self
5
+ FORMATTERS = {
6
+ json: Grape::Formatter::Json,
7
+ jsonapi: Grape::Formatter::Json,
8
+ serializable_hash: Grape::Formatter::SerializableHash,
9
+ txt: Grape::Formatter::Txt,
10
+ xml: Grape::Formatter::Xml
11
+ }
12
+
13
+ def formatters(options)
14
+ FORMATTERS.merge(options[:formatters] || {})
15
+ end
16
+
17
+ def formatter_for(api_format, options = {})
18
+ spec = formatters(options)[api_format]
19
+ case spec
20
+ when nil
21
+ lambda { |obj, env| obj }
22
+ when Symbol
23
+ method(spec)
24
+ else
25
+ spec
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ module Grape
2
+ module Formatter
3
+ module Json
4
+ class << self
5
+ def call(object, env)
6
+ return object.to_json if object.respond_to?(:to_json)
7
+ MultiJson.dump(object)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module Grape
2
+ module Formatter
3
+ module SerializableHash
4
+ class << self
5
+ def call(object, env)
6
+ return object if object.is_a?(String)
7
+ return MultiJson.dump(serialize(object)) if serializable?(object)
8
+ return object.to_json if object.respond_to?(:to_json)
9
+ MultiJson.dump(object)
10
+ end
11
+
12
+ private
13
+
14
+ def serializable?(object)
15
+ object.respond_to?(:serializable_hash) || object.kind_of?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false) || object.kind_of?(Hash)
16
+ end
17
+
18
+ def serialize(object)
19
+ if object.respond_to? :serializable_hash
20
+ object.serializable_hash
21
+ elsif object.kind_of?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false)
22
+ object.map { |o| o.serializable_hash }
23
+ elsif object.kind_of?(Hash)
24
+ object.inject({}) do |h, (k, v)|
25
+ h[k] = serialize(v)
26
+ h
27
+ end
28
+ else
29
+ object
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ module Grape
2
+ module Formatter
3
+ module Txt
4
+ class << self
5
+ def call(object, env)
6
+ object.respond_to?(:to_txt) ? object.to_txt : object.to_s
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Grape
2
+ module Formatter
3
+ module Xml
4
+ class << self
5
+ def call(object, env)
6
+ return object.to_xml if object.respond_to?(:to_xml)
7
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module Grape
2
+ class Request < Rack::Request
3
+ def params
4
+ @params ||= begin
5
+ params = Hashie::Mash.new(super)
6
+ if env['rack.routing_args']
7
+ args = env['rack.routing_args'].dup
8
+ # preserve version from query string parameters
9
+ args.delete(:version)
10
+ params.deep_merge!(args)
11
+ end
12
+ params
13
+ end
14
+ end
15
+
16
+ def headers
17
+ @headers ||= env.dup.inject({}) do |h, (k, v)|
18
+ if k.to_s.start_with? 'HTTP_'
19
+ k = k[5..-1].gsub('_', '-').downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
20
+ h[k] = v
21
+ end
22
+ h
23
+ end
24
+ end
25
+ end
26
+ end