apicasso 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5eeaa0a52a601b6fce1ea00f8bf9b236ca60f432ed9f676586a13f8433764d0a
4
- data.tar.gz: 426f5dad526ac3c86bd3ea8764c035a26bf801462c6f9de069924157e914547d
3
+ metadata.gz: d09d0e339bfb17aa767876e5041f749e5154cedb2c327588713a14113d0e00dd
4
+ data.tar.gz: dd03efe8e6a7a64e4f67cae8e19f2d8be5a0782f10d1f0524114f3071969bbca
5
5
  SHA512:
6
- metadata.gz: dcd5b7f8a9613413bfb6b9b9e6f342d802e0521f169b85f36bd24198476ac8a1cc0b75e9494703c174f78e1bff73369d9fcc126cbd6ecded961b5610c819bae5
7
- data.tar.gz: c993d4f705a331eec2506781ba15a98409bfee179cc93a09d9068d78d5292b1edb4a1eeb20dee4c05bc3bf37e9f0c2398cef964b1f1c5c75a954bf05e01dc97e
6
+ metadata.gz: a456ef2a8afb8c238f99b693cbd3bf650753fb6c07f6c5e31724cd03e7d5607cb6edfbc67004521e1abcccbf13f80b628ba197fef4876993cfd1c8a4a6f36aae
7
+ data.tar.gz: 718d3734733cbeee045546495cd5f26985c799257172ca0434df0a7bd7db64e8a165c537ac17d1fda4f0cf8e3f82343bc9f8aa852e3d70958ffe4e4905ab77da
data/README.md CHANGED
@@ -11,7 +11,7 @@ You can make your own API with only 4 steps:
11
11
  ### Step 1
12
12
  Create your models
13
13
  ### Step 2
14
- Insert **APIcasso** engine into your routes
14
+ Insert **APIcasso** engine into your routes and run the installation command
15
15
  ### Step 3
16
16
  [Create an Apicasso::Key](https://github.com/autoforce/APIcasso#authorization)
17
17
  ### Step 4
@@ -36,6 +36,7 @@ $ bundle install && rails g apicasso:install
36
36
 
37
37
  - PostgreSQL with JSON columns support
38
38
  - Ruby 2.3+
39
+ - Rails 5+
39
40
 
40
41
  # Usage
41
42
 
@@ -90,6 +91,10 @@ And in your `app/controllers/custom_controller.rb` you would have something like
90
91
 
91
92
  This way you enjoy all our object finder, authorization and authentication features, making your job more straight into your business logic.
92
93
 
94
+ ## CORS
95
+
96
+ APIcasso comes with a permissive CORS configuration out of the box. But you can make your own by editting the `config/initializers/apicasso.rb` file, which is created at the installation proccess. The file comes with some descriptive comments and all configuration is based on [Rack CORS](https://github.com/cyu/rack-cors) options.
97
+
93
98
  ## Authentication
94
99
 
95
100
  > But exposing my models to the internet is permissive as hell! Haven't you thought about security?
@@ -223,7 +228,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Ervalh
223
228
  ### TODO
224
229
 
225
230
  - Add support to other databases
226
- - [Abstract a configurable CORS approach, maybe using middleware](https://github.com/autoforce/APIcasso/issues/22)
227
231
  - Add gem options like: Token rotation, Alternative authentication methods
228
232
  - Refine and document auto-documentation feature
229
233
  - Rate limiting
@@ -60,91 +60,13 @@ module Apicasso
60
60
  def response_metadata
61
61
  {
62
62
  status: response.status,
63
- body: (response.body.present? ? JSON.parse(response.body) : '')
63
+ body: parsed_response
64
64
  }
65
65
  end
66
66
 
67
- # Reutrns root_resource if nested_resource is not set scoped by permissions
68
- def resource
69
- (@nested_resource || @root_resource)
70
- end
71
-
72
- # A method to extract all assosciations available
73
- def associations_array
74
- resource.reflect_on_all_associations.map { |association| association.name.to_s }
75
- end
76
-
77
- # Used to avoid errors parsing the search query, which can be passed as
78
- # a JSON or as a key-value param. JSON is preferred because it generates
79
- # shorter URLs on GET parameters.
80
- def parsed_query
81
- JSON.parse(params[:q])
82
- rescue JSON::ParserError, TypeError
83
- params[:q]
84
- end
85
-
86
- # Used to avoid errors in included associations parsing and to enable a
87
- # insertion point for a change on splitting method.
88
- def parsed_associations
89
- params[:include].split(',').map do |param|
90
- if @object.respond_to?(param)
91
- param if associations_array.include?(param)
92
- end
93
- end.compact
94
- rescue NoMethodError
95
- []
96
- end
97
-
98
- # Used to avoid errors in included associations parsing and to enable a
99
- # insertion point for a change on splitting method.
100
- def parsed_methods
101
- params[:include].split(',').map do |param|
102
- if @object.respond_to?(param)
103
- param unless associations_array.include?(param)
104
- end
105
- end.compact
106
- rescue NoMethodError
107
- []
108
- end
109
-
110
- # Used to avoid errors in fieldset selection parsing and to enable a
111
- # insertion point for a change on splitting method.
112
- def parsed_select
113
- params[:select].split(',').map do |field|
114
- field if resource.column_names.include?(field)
115
- end
116
- rescue NoMethodError
117
- []
118
- end
119
-
120
- # Receives a `.paginate`d collection and returns the pagination
121
- # metadata to be merged into response
122
- def pagination_metadata_for(records)
123
- { total: records.total_entries,
124
- total_pages: records.total_pages,
125
- last_page: records.next_page.blank?,
126
- previous_page: previous_link_for(records),
127
- next_page: next_link_for(records),
128
- out_of_bounds: records.out_of_bounds?,
129
- offset: records.offset }
130
- end
131
-
132
- # Generates a contextualized URL of the next page for the request
133
- def next_link_for(records)
134
- uri = URI.parse(request.original_url)
135
- query = Rack::Utils.parse_query(uri.query)
136
- query['page'] = records.next_page
137
- uri.query = Rack::Utils.build_query(query)
138
- uri.to_s
139
- end
140
-
141
- # Generates a contextualized URL of the previous page for the request
142
- def previous_link_for(records)
143
- uri = URI.parse(request.original_url)
144
- query = Rack::Utils.parse_query(uri.query)
145
- query['page'] = records.previous_page
146
- uri.query = Rack::Utils.build_query(query)
147
- uri.to_s
67
+ # Parsed response to save as metadata for the requests
68
+ def parsed_response
69
+ (response.body.present? ? JSON.parse(response.body) : '')
148
70
  end
149
71
 
150
72
  # Receives a `:action, :resource, :object` hash to validate authorization
@@ -8,6 +8,7 @@ module Apicasso
8
8
  before_action :set_nested_resource, only: %i[nested_index]
9
9
  before_action :set_records, only: %i[index nested_index]
10
10
  include SqlSecurity
11
+ include CrudUtils
11
12
  include Orderable
12
13
  # GET /:resource
13
14
  # Returns a paginated, ordered and filtered query based response.
@@ -90,10 +91,6 @@ module Apicasso
90
91
  authorize! action_to_cancancan, @object
91
92
  end
92
93
 
93
- def action_to_cancancan
94
- action_name == 'nested_index' ? :index : action_name.to_sym
95
- end
96
-
97
94
  # Used to setup the resource's schema, mapping attributes and it's types
98
95
  def resource_schema
99
96
  schemated = {}
@@ -169,12 +166,6 @@ module Apicasso
169
166
  total: @records.size }
170
167
  end
171
168
 
172
- # Parse to include options
173
- def include_options
174
- { include: parsed_associations || [],
175
- methods: parsed_methods || [] }
176
- end
177
-
178
169
  # Parsed JSON to be used as response payload, with included relations
179
170
  def include_relations
180
171
  @records = @records.includes(parsed_associations)
@@ -196,41 +187,6 @@ module Apicasso
196
187
  .permit(resource_params)
197
188
  end
198
189
 
199
- # Resource params mapping, with a twist:
200
- # Including relations as they are needed
201
- def resource_params
202
- built = resource_schema.keys
203
- built += has_one_params if has_one_params.present?
204
- built += has_many_params if has_many_params.present?
205
- built
206
- end
207
-
208
- # A wrapper to has_one relations parameter building
209
- def has_one_params
210
- resource.reflect_on_all_associations(:has_one).map do |one|
211
- if one.class_name.starts_with?('ActiveStorage')
212
- next if one.class_name.ends_with?('Blob')
213
-
214
- one.name.to_s.gsub(/(_attachment)$/, '').to_sym
215
- else
216
- one.name
217
- end
218
- end.compact
219
- end
220
-
221
- # A wrapper to has_many parameter building
222
- def has_many_params
223
- resource.reflect_on_all_associations(:has_many).map do |many|
224
- if many.class_name.starts_with?('ActiveStorage')
225
- next if many.class_name.ends_with?('Blob')
226
-
227
- { many.name.to_s.gsub(/(_attachments)$/, '').to_sym => [] }
228
- else
229
- { many.name.to_sym => [] }
230
- end
231
- end.compact
232
- end
233
-
234
190
  # Common setup to stablish which model is the resource of this request
235
191
  def set_root_resource
236
192
  @root_resource = params[:resource].classify.constantize
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module to extract utilities used on CRUD controllers.
4
+ # It makes it easier to parse parameters, proccess requests
5
+ # and build rich responses.
6
+ module CrudUtils
7
+ extend ActiveSupport::Concern
8
+
9
+ # Reutrns root_resource if nested_resource is not set scoped by permissions
10
+ def resource
11
+ (@nested_resource || @root_resource)
12
+ end
13
+
14
+ # A method to extract all assosciations available
15
+ def associations_array
16
+ resource.reflect_on_all_associations.map { |association| association.name.to_s }
17
+ end
18
+
19
+ # An parser to the action name so that nested_index has the same
20
+ # authorization behavior as index
21
+ def action_to_cancancan
22
+ action_name == 'nested_index' ? :index : action_name.to_sym
23
+ end
24
+
25
+ # Resource params mapping, with a twist:
26
+ # Including relations as they are needed
27
+ def resource_params
28
+ built = resource_schema.keys
29
+ built += has_one_params if has_one_params.present?
30
+ built += has_many_params if has_many_params.present?
31
+ built
32
+ end
33
+
34
+ # A wrapper to has_one relations parameter building
35
+ def has_one_params
36
+ resource.reflect_on_all_associations(:has_one).map do |one|
37
+ if one.class_name.starts_with?('ActiveStorage')
38
+ next if one.class_name.ends_with?('Blob')
39
+
40
+ one.name.to_s.gsub(/(_attachment)$/, '').to_sym
41
+ else
42
+ one.name
43
+ end
44
+ end.compact
45
+ end
46
+
47
+ # A wrapper to has_many parameter building
48
+ def has_many_params
49
+ resource.reflect_on_all_associations(:has_many).map do |many|
50
+ if many.class_name.starts_with?('ActiveStorage')
51
+ next if many.class_name.ends_with?('Blob')
52
+
53
+ { many.name.to_s.gsub(/(_attachments)$/, '').to_sym => [] }
54
+ else
55
+ { many.name.to_sym => [] }
56
+ end
57
+ end.compact
58
+ end
59
+
60
+ # Parse to include options
61
+ def include_options
62
+ { include: parsed_associations || [],
63
+ methods: parsed_methods || [] }
64
+ end
65
+
66
+ # Used to avoid errors parsing the search query, which can be passed as
67
+ # a JSON or as a key-value param. JSON is preferred because it generates
68
+ # shorter URLs on GET parameters.
69
+ def parsed_query
70
+ JSON.parse(params[:q])
71
+ rescue JSON::ParserError, TypeError
72
+ params[:q]
73
+ end
74
+
75
+ # Used to avoid errors in included associations parsing and to enable a
76
+ # insertion point for a change on splitting method.
77
+ def parsed_associations
78
+ params[:include].split(',').map do |param|
79
+ if @object.respond_to?(param)
80
+ param if associations_array.include?(param)
81
+ end
82
+ end.compact
83
+ rescue NoMethodError
84
+ []
85
+ end
86
+
87
+ # Used to avoid errors in included associations parsing and to enable a
88
+ # insertion point for a change on splitting method.
89
+ def parsed_methods
90
+ params[:include].split(',').map do |param|
91
+ if @object.respond_to?(param)
92
+ param unless associations_array.include?(param)
93
+ end
94
+ end.compact
95
+ rescue NoMethodError
96
+ []
97
+ end
98
+
99
+ # Used to avoid errors in fieldset selection parsing and to enable a
100
+ # insertion point for a change on splitting method.
101
+ def parsed_select
102
+ params[:select].split(',').map do |field|
103
+ field if resource.column_names.include?(field)
104
+ end
105
+ rescue NoMethodError
106
+ []
107
+ end
108
+
109
+ # Receives a `.paginate`d collection and returns the pagination
110
+ # metadata to be merged into response
111
+ def pagination_metadata_for(records)
112
+ { total: records.total_entries,
113
+ total_pages: records.total_pages,
114
+ last_page: records.next_page.blank?,
115
+ previous_page: previous_link_for(records),
116
+ next_page: next_link_for(records),
117
+ out_of_bounds: records.out_of_bounds?,
118
+ offset: records.offset }
119
+ end
120
+
121
+ # Generates a contextualized URL of the next page for the request
122
+ def next_link_for(records)
123
+ page_link(records, page: 'next')
124
+ end
125
+
126
+ # Generates a contextualized URL of the previous page for the request
127
+ def previous_link_for(records)
128
+ page_link(records, page: 'previous')
129
+ end
130
+
131
+ # Common pagination link generation helper
132
+ def page_link(records, opts = {})
133
+ uri = URI.parse(request.original_url)
134
+ query = Rack::Utils.parse_query(uri.query)
135
+ query['page'] = records.send("#{opts[:page]}_page")
136
+ uri.query = Rack::Utils.build_query(query)
137
+ uri.to_s
138
+ end
139
+ end
@@ -46,12 +46,13 @@ module SqlSecurity
46
46
 
47
47
  # Check for a bad request to be more secure
48
48
  def klasses_allowed
49
- raise ActionController::BadRequest.new('Bad hacker, stop be bully or I will tell to your mom!') unless descendants_included?
49
+ raise ActionController::BadRequest.new('Bad hacker, stop be bully or I will tell to your mom!') unless safe_resource?
50
50
  end
51
51
 
52
- # Check if it's a descendant model allowed
53
- def descendants_included?
54
- DESCENDANTS_UNDERSCORED.include?(param_attribute.to_s.underscore)
52
+ # Check if it's safe to use the requet
53
+ def safe_resource?
54
+ controller_name == representative_resource ||
55
+ DESCENDANTS_UNDERSCORED.include?(param_attribute.to_s.underscore)
55
56
  end
56
57
 
57
58
  # Get param to be compared
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A Module to rule them all...
1
4
  module Apicasso
2
- VERSION = '0.6.4'.freeze
5
+ # Current gem version
6
+ VERSION = '0.6.5'.freeze
3
7
  end