grape-security 0.8.0

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.
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