jpie 3.1.3 → 3.1.4

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: dd99f20db06f6a541724cee1eb5c96adc2045933bc29dfff0ab55e1eaf03753c
4
- data.tar.gz: 54b68ed6a85b0091bbafdde4c87fd5bfb18d8d35df1afc9c3233a6c0c6b68976
3
+ metadata.gz: a865a73571e8287482862cc1bda700a579b036108f3c9bb2c752e197ed6b2793
4
+ data.tar.gz: 03b2fe7e47c5712440053ddabe9d131499c34db1a730a2b88b9970900ecda76a
5
5
  SHA512:
6
- metadata.gz: 4c319de1c2020500399d8ec1fe78392f3b2c36e147edda8405100a38bba428c4aa8229f2da61be1ea109bfd616c563e4907d9be4ad364438070d5c872e340544
7
- data.tar.gz: ea1bd9693e915dc19a9b96258d201aba8af05a4ebb9778a2fc6868336bf8cbc32276cd841c85ffedb0a7da8e8e16b21fc8e7798e46db95487c86a036bdcc5173
6
+ metadata.gz: f397a98e0ef1b4759761f84d271694102f181b805d0619a4efca42f1bc4fc407870019bf179a5bcf93e3c5745f0998e9b665c266da8fad496e9a8bbb7240b6d1
7
+ data.tar.gz: 4261ae97d8e83de26145b5d85d62da266d933148d154088f57c54066e71b77c7761d1d6e0d6bf64dd8258fdca34c6547c2ba853dbbf85bbedf6bdffa2dac0b76
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jpie (3.1.2)
4
+ jpie (3.1.3)
5
5
  actionpack (~> 8.1, >= 8.1.0)
6
6
  pg_query (>= 4)
7
7
  prosopite (>= 1)
@@ -8,7 +8,7 @@ module JSONAPI
8
8
  private
9
9
 
10
10
  def prepare_create_params(sti_class)
11
- hash = deserialize_params(:create, model_class: sti_class)
11
+ hash = deserialize_params(:create, model_class: sti_class, definition: determine_sti_resource_class)
12
12
  attachments = extract_active_storage_params_from_hash(hash, sti_class)
13
13
  clean_params(hash, attachments)
14
14
  apply_sti_type(sti_class, hash)
@@ -16,7 +16,7 @@ module JSONAPI
16
16
  end
17
17
 
18
18
  def prepare_create_params_from_data(sti_class, data)
19
- hash = deserialize_params(:create, model_class: sti_class, data:)
19
+ hash = deserialize_params(:create, model_class: sti_class, data:, definition: determine_sti_resource_class)
20
20
  attachments = extract_active_storage_params_from_hash(hash, sti_class)
21
21
  clean_params(hash, attachments)
22
22
  apply_sti_type(sti_class, hash)
@@ -24,7 +24,7 @@ module JSONAPI
24
24
  end
25
25
 
26
26
  def prepare_update_params
27
- hash = deserialize_params(:update)
27
+ hash = deserialize_params(:update, definition: @resource_class)
28
28
  attachments = extract_active_storage_params_from_hash(hash, model_class)
29
29
  clean_params(hash, attachments)
30
30
  [hash, attachments]
@@ -51,10 +51,10 @@ module JSONAPI
51
51
  JSONAPI::ParamHelpers.deep_symbolize_params(data)
52
52
  end
53
53
 
54
- def deserialize_params(action = :update, model_class: nil, data: nil)
54
+ def deserialize_params(action = :update, model_class: nil, data: nil, definition: nil)
55
55
  target = model_class || self.model_class
56
56
  payload = data.nil? ? raw_jsonapi_data : symbolize_data(data)
57
- JSONAPI::Deserializer.new(payload, model_class: target, action:).to_model_attributes
57
+ JSONAPI::Deserializer.new(payload, model_class: target, action:, definition:).to_model_attributes
58
58
  end
59
59
 
60
60
  def resource_url(resource)
@@ -90,6 +90,10 @@ module JSONAPI
90
90
  def ensure_relationship_writable!(association)
91
91
  return if active_storage_writable?(association)
92
92
 
93
+ unless @resource_class.relationship_field_writable?(@relationship_name, :update)
94
+ raise JSONAPI::AuthorizationError, "Relationship '#{@relationship_name}' is not updatable"
95
+ end
96
+
93
97
  relationship_def = find_relationship_definition
94
98
  readonly = relationship_def && (relationship_def[:options] || {})[:readonly] == true
95
99
 
@@ -41,6 +41,21 @@ module JSONAPI
41
41
  resolve_field_list(:@updatable_fields, :permitted_updatable_fields)
42
42
  end
43
43
 
44
+ # Whether a relationship may be written for the given action (:create or :update).
45
+ # A relationship is gated by the creatable_fields/updatable_fields allow-list only when
46
+ # the resource opted it in by naming it in one of those lists; relationships named in
47
+ # neither keep the default (readonly-only) behavior. This lets a resource make a
48
+ # relationship create-only — name it in creatable_fields, omit it from updatable_fields —
49
+ # the same way it restricts attributes.
50
+ def relationship_field_writable?(name, action)
51
+ name = name.to_sym
52
+ creatable = permitted_creatable_fields.map(&:to_sym)
53
+ updatable = permitted_updatable_fields.map(&:to_sym)
54
+ return true unless creatable.include?(name) || updatable.include?(name)
55
+
56
+ (action == :create ? creatable : updatable).include?(name)
57
+ end
58
+
44
59
  def resolve_field_list(ivar, method)
45
60
  return (instance_variable_get(ivar) || []).uniq if instance_variable_defined?(ivar)
46
61
  return superclass.public_send(method).uniq if inherits_field?(ivar, method)
@@ -129,7 +129,8 @@ module JSONAPI
129
129
 
130
130
  association = @model_class.reflect_on_association(association_name.to_sym)
131
131
  readonly = relationship_options_for(association_name)[:readonly] == true
132
- JSONAPI::RelationshipGuard.ensure_writable!(association, association_name, readonly:) if association
132
+ field_writable = @definition.relationship_field_writable?(association_name, @action)
133
+ JSONAPI::RelationshipGuard.ensure_writable!(association, association_name, readonly:, field_writable:)
133
134
  end
134
135
 
135
136
  def relationship_options_for(association_name)
@@ -16,10 +16,14 @@ module JSONAPI
16
16
  include Serialization::RelationshipProcessing
17
17
  include Serialization::DeserializationHelpers
18
18
 
19
- def initialize(params, model_class:, action: :create)
19
+ def initialize(params, model_class:, action: :create, definition: nil)
20
20
  @params = ParamHelpers.deep_symbolize_params(params)
21
21
  @model_class = model_class
22
- @definition = ResourceLoader.find_for_model(model_class)
22
+ # Prefer the resource the request is addressing (resolved by type/route). Resolving
23
+ # from the model alone is ambiguous when several resources share one model
24
+ # (e.g. RestrictedPostResource and PostResource both back Post), which would gate
25
+ # relationships against the wrong resource's creatable_fields/updatable_fields.
26
+ @definition = definition || ResourceLoader.find_for_model(model_class)
23
27
  @action = action.to_sym
24
28
  end
25
29
  end
@@ -6,7 +6,12 @@ module JSONAPI
6
6
  module RelationshipGuard
7
7
  module_function
8
8
 
9
- def ensure_writable!(association, error_target, readonly: false)
9
+ def ensure_writable!(association, error_target, readonly: false, field_writable: true)
10
+ # A relationship excluded from the resource's creatable_fields/updatable_fields
11
+ # allow-list is not writable for this action, on either the main-body path or the
12
+ # dedicated endpoint. This gate is independent of the AR reflection.
13
+ raise JSONAPI::AuthorizationError, "Relationship '#{error_target}' is not writable" unless field_writable
14
+
10
15
  return unless association
11
16
  return unless readonly
12
17
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "3.1.3"
4
+ VERSION = "3.1.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpie
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.3
4
+ version: 3.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Kampp