rest_framework 2.0.0.beta1 → 2.0.0.beta2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6551a744b8574c83f8b5a482e32337e2ea554e4c572baf7c207af3bcda26fb38
4
- data.tar.gz: d26a407bcf834d76a6f1d63e7ca22eafe05cd90197a50d53b016e178a2d92dd4
3
+ metadata.gz: 639dcc597e17e5148cde3f7511637913be7b431899f59a5cc7f504533969233e
4
+ data.tar.gz: 0c732ab6c80f9c06b29e4a30e29087ff4fa65206df15b7826ffd9f0d2781b774
5
5
  SHA512:
6
- metadata.gz: b66138a6abd154cc8df980b6a533273dc5f6e5fd07bcb6d381e73254018cc065a1a2b01c6ae086f0eb6274a11501624a80f56ffd0f58657aa4f7c62145b74f7c
7
- data.tar.gz: 8222dd29fb6de099e6f555438dcf1328a251a28f20076837176938ebc555a4c1f3d52df0acc77507150f6217058ce8f9dfa53f003641c1ab0e3914d515505348
6
+ metadata.gz: 656e302db2252a27a30508e1f5c1d2caf2d603288d4ca3e2ec0995355a4a6501b18bc8320463a17113568a562ead98c3896c949a00aa03cc83a6c71f2f62d738
7
+ data.tar.gz: f760d0140360ca1856dca0ab87b8de1903e09fd7cc442c1b40c9150b7abaf312f43ae67a52a5d0881af9e1f98489a074a82ebaef87b527c99e6e8f7175c67740
data/README.md CHANGED
@@ -202,8 +202,10 @@ changes; the migration checklist that follows walks through updating an existing
202
202
  - **Delegated actions wrap their result under a `return` key**, and raise on a missing or non-public
203
203
  target instead of silently 404-ing.
204
204
  - **Removed** `rrf_finalize` and the `auto_finalize` / `freeze_config` hooks.
205
- - **Security hardening** — per-element read-only stripping on bulk writes, an ordering-oracle fix,
206
- sanitized `StatementInvalid` messages, and safer query-filter parsing.
205
+ - **Security hardening** — `find_by`, filtering, ordering, and search are scoped to serialized,
206
+ non-`write_only` fields (so hidden/secret columns can't be used as lookup/enumeration keys), plus
207
+ per-element read-only stripping on bulk writes, an ordering-oracle fix, sanitized
208
+ `StatementInvalid` messages, and safer query-filter parsing.
207
209
 
208
210
  ### Migrating from Older Versions
209
211
 
@@ -217,8 +219,8 @@ See the guide for details on each item.
217
219
  - [ ] Render custom actions with `render(api: ...)`, replacing the older `api_response(...)` /
218
220
  `render_api(...)`.
219
221
  - [ ] Replace `rest_resource` / `rest_resources` / `rest_root` with `rest_route`.
220
- - Fold any dedicated root controller into the namespace's base controller, which now serves the
221
- root via `index_content` (the standalone `root` action and `rest_root` are gone).
222
+ - [ ] Fold any dedicated root controller into the namespace's base controller, which now serves the
223
+ root via `index_content` (the standalone `root` action and `rest_root` are gone).
222
224
  - [ ] Rename `singleton_controller` → `singular`.
223
225
  - [ ] Remove `rrf_finalize` calls and the `auto_finalize` / `freeze_config` config (all gone).
224
226
  - [ ] Expect paginated `index` responses by default (`self.paginator_class = nil` restores a bare
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta1
1
+ 2.0.0.beta2
@@ -253,33 +253,29 @@ module RESTFramework::Controller
253
253
  self.model&.human_attribute_name(s, default: default_title) || default_title
254
254
  end
255
255
 
256
- # Get the available fields. Fallback to this controller's model columns, or an empty array. This
257
- # should always return an array of strings.
258
- def get_fields(input_fields: nil)
259
- input_fields ||= self.fields
260
-
261
- # If fields is a hash, then parse it.
262
- if input_fields.is_a?(Hash)
263
- return RESTFramework::Utils.parse_fields_hash(
264
- input_fields,
256
+ # Resolve the `fields` config to an array of strings. Memoized, since `fields` and the flags it
257
+ # depends on are class-level config fixed at load time.
258
+ def get_fields
259
+ @get_fields ||= if self.fields.is_a?(Hash)
260
+ RESTFramework::Utils.parse_fields_hash(
261
+ self.fields,
265
262
  self.model,
266
263
  exclude_associations: self.exclude_associations,
267
264
  action_text: self.enable_action_text,
268
265
  active_storage: self.enable_active_storage,
269
266
  )
270
- elsif !input_fields
271
- # Otherwise, if fields is nil, then fallback to columns.
272
- return self.model ? RESTFramework::Utils.fields_for(
267
+ elsif self.fields
268
+ self.fields.map(&:to_s)
269
+ elsif self.model
270
+ RESTFramework::Utils.fields_for(
273
271
  self.model,
274
272
  exclude_associations: self.exclude_associations,
275
273
  action_text: self.enable_action_text,
276
274
  active_storage: self.enable_active_storage,
277
- ) : []
278
- elsif input_fields
279
- input_fields = input_fields.map(&:to_s)
275
+ )
276
+ else
277
+ []
280
278
  end
281
-
282
- input_fields
283
279
  end
284
280
 
285
281
  # Get a full field configuration, including defaults and inferred values.
@@ -647,7 +643,27 @@ module RESTFramework::Controller
647
643
  end
648
644
 
649
645
  def get_fields
650
- self.class.get_fields(input_fields: self.class.fields)
646
+ self.class.get_fields
647
+ end
648
+
649
+ def readable_fields
650
+ cfg = self.class.field_configuration
651
+ self.get_fields.reject { |f| cfg[f]&.[](:write_only) }
652
+ end
653
+
654
+ # `readable_fields` restricted to real columns, for query surfaces that build SQL directly
655
+ # (find_by, search) and would raise on a virtual/method field.
656
+ def readable_columns
657
+ self.readable_fields & self.class.model.column_names
658
+ end
659
+
660
+ # `readable_fields` restricted to columns and associations, for surfaces that also resolve dotted
661
+ # `association.sub_field` paths (filtering, ordering). Excludes virtual/method fields, which have
662
+ # no column to order or filter by.
663
+ def readable_columns_or_associations
664
+ cfg = self.class.field_configuration
665
+ columns = self.class.model.column_names
666
+ self.readable_fields.select { |f| f.in?(columns) || cfg[f]&.[](:kind) == "association" }
651
667
  end
652
668
 
653
669
  # Get a hash of strong parameters for the current action.
@@ -657,7 +673,8 @@ module RESTFramework::Controller
657
673
  @_get_allowed_parameters = self.class.allowed_parameters
658
674
  return @_get_allowed_parameters if @_get_allowed_parameters
659
675
 
660
- # Assemble strong parameters.
676
+ # Assemble strong parameters. Read-only fields are permitted here and stripped later per-action
677
+ # by `_rrf_strip_read_only_fields` (which keeps the primary key on bulk update to find records).
661
678
  variations = []
662
679
  hash_variations = {}
663
680
  reflections = self.class.model.reflections
@@ -868,9 +885,10 @@ module RESTFramework::Controller
868
885
  # Find by another column if it's permitted.
869
886
  if find_by_param = self.class.find_by_query_param.presence
870
887
  if find_by = request.query_parameters[find_by_param].presence
871
- find_by_fields = (
872
- self.class.find_by_fields&.map(&:to_s) || self.class.model.columns_hash.keys
873
- )
888
+ # Default to readable columns: excluding write_only keeps hidden values from being used as
889
+ # lookup keys, and restricting to real columns keeps virtual/method fields from reaching a
890
+ # doomed `find_by(<not a column>)` (which would raise on the DB).
891
+ find_by_fields = self.class.find_by_fields&.map(&:to_s) || self.readable_columns
874
892
 
875
893
  # A `find_by` was explicitly requested, so it must be a permitted field.
876
894
  raise ActiveRecord::RecordNotFound unless find_by.in?(find_by_fields)
@@ -1,7 +1,8 @@
1
1
  # A filter backend which handles ordering of the recordset.
2
2
  class RESTFramework::Filters::OrderingFilter < RESTFramework::Filters::BaseFilter
3
3
  def _get_fields
4
- @controller.class.ordering_fields&.map(&:to_s) || @controller.get_fields
4
+ # Always return a list of strings; `@controller.readable_columns_or_associations` already does.
5
+ @controller.class.ordering_fields&.map(&:to_s) || @controller.readable_columns_or_associations
5
6
  end
6
7
 
7
8
  # Convert the ordering param into an `[ordering, references]` pair: the ordering config for
@@ -43,8 +43,8 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
43
43
  ARRAY_PREDICATES = %i[in not].freeze
44
44
 
45
45
  def _get_fields
46
- # Always return a list of strings; `@controller.get_fields` already does this.
47
- @controller.class.filter_fields&.map(&:to_s) || @controller.get_fields
46
+ # Always return a list of strings; `@controller.readable_columns_or_associations` already does.
47
+ @controller.class.filter_fields&.map(&:to_s) || @controller.readable_columns_or_associations
48
48
  end
49
49
 
50
50
  # Helper to find a variation of a field using a predicate. For example, there could be a field
@@ -1,13 +1,9 @@
1
1
  class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
2
2
  def _get_fields
3
- if search_fields = @controller.class.search_fields
4
- return search_fields&.map(&:to_s)
5
- end
6
-
7
- columns = @controller.class.model.column_names
8
- @controller.get_fields.select { |f|
9
- f.in?(RESTFramework.config.search_columns) && f.in?(columns)
10
- }
3
+ # Always return a list of strings; `@controller.readable_columns` already does.
4
+ @controller.class.search_fields&.map(&:to_s) || (
5
+ @controller.readable_columns & RESTFramework.config.search_columns
6
+ )
11
7
  end
12
8
 
13
9
  # Filter data according to the request query parameters.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta1
4
+ version: 2.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregory N. Schmit