spree_core 5.5.3 → 5.5.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: 2edebb88f8cab59b634a4bbbae5a24b15feab2d7673ecd521d70a1265b4e7bd0
4
- data.tar.gz: 1fa56dfbe5ebd747212b1608490c5414246cfad12e721f49d040c97f8299ef33
3
+ metadata.gz: d1bfb936ec246953ec3805bd017b32594f42bb168c5373dd3ed86d4a219efd6d
4
+ data.tar.gz: 273695ba130dafae60be7bb880cb6bae32722d82bf8dc3d49ee11943049c1c65
5
5
  SHA512:
6
- metadata.gz: 20f0b006c40c37d8e8cbf25d97b99a73e9f3ce72d6a4903f6238aec8cad435f45c1f6517cfb54356732f7c0f69910e1013f45b7a344bc29506c888521661f46a
7
- data.tar.gz: c72003a7f9002a1443ef60fa19f70fe6aa4e4acf9f466012101889bd964c7aaf806d9f136fe158453c751d4f11dee74f4c42b9c9e1d39ec82b3994a5293337ee
6
+ metadata.gz: 412b093fc9621b695691bcbd3b3b9cb4335d42f17fe62b81b820ed67acabdabbc9b16954ae4331cac03d58fabb43217b72d68dd1bc4992d6ee13f203fcad4b71
7
+ data.tar.gz: 9e325093542d1a77466ff4780e61aae47d309daeba2f583723f00bb83c906f26ee085512b1bb85caae53d4c731f1a2ff309541878c675a2736f397e98931001d
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Keeps single-use credentials out of the persisted webhook delivery log.
5
+ #
6
+ # Some events must carry a live credential to their subscriber — a storefront
7
+ # that owns its transactional emails needs the real password reset token to
8
+ # build the email. Delivering it over TLS to a merchant-configured endpoint is
9
+ # intended; keeping a readable copy in `spree_webhook_deliveries.payload` is
10
+ # not, because that column is queryable and is served back through the Admin
11
+ # API delivery log.
12
+ #
13
+ # Sensitive values are therefore replaced with {REDACTION_PLACEHOLDER} before
14
+ # the record is written, and re-attached in memory at send time so the
15
+ # outgoing request body is unchanged.
16
+ module WebhookPayloadRedaction
17
+ extend ActiveSupport::Concern
18
+
19
+ # Payload keys under `data` whose values are live credentials.
20
+ SENSITIVE_PAYLOAD_KEYS = %w[reset_token unsubscribe_token].freeze
21
+
22
+ REDACTION_PLACEHOLDER = '[REDACTED]'
23
+
24
+ # Splits a payload into the version safe to persist and the secrets held
25
+ # back from it.
26
+ #
27
+ # @param payload [Hash] the full event payload
28
+ # @return [Array(Hash, Hash)] redacted payload, and the extracted secrets
29
+ # keyed as they appeared under `data`
30
+ def self.split(payload)
31
+ secrets = {}
32
+
33
+ redacted = transform_data_hashes(payload) do |data|
34
+ data.to_h do |key, value|
35
+ if SENSITIVE_PAYLOAD_KEYS.include?(key.to_s) && value.present?
36
+ secrets[secret_key_for(key)] = value
37
+ [key, REDACTION_PLACEHOLDER]
38
+ else
39
+ [key, value]
40
+ end
41
+ end
42
+ end
43
+
44
+ secrets.empty? ? [payload, {}] : [redacted, secrets]
45
+ end
46
+
47
+ # Re-attaches previously extracted secrets to a redacted payload.
48
+ #
49
+ # @param payload [Hash] the redacted payload
50
+ # @param secrets [Hash] secrets returned by {split}
51
+ # @return [Hash] the payload as it should go over the wire
52
+ def self.merge(payload, secrets)
53
+ return payload if secrets.blank?
54
+
55
+ transform_data_hashes(payload) do |data|
56
+ data.to_h do |key, value|
57
+ secret = secrets[secret_key_for(key)]
58
+ [key, secret.presence || value]
59
+ end
60
+ end
61
+ end
62
+
63
+ # Applies +block+ to every `data` hash on the payload.
64
+ #
65
+ # Both key forms are visited rather than only the first match: a payload
66
+ # carrying `:data` *and* `'data'` would otherwise leave one of them
67
+ # unredacted.
68
+ def self.transform_data_hashes(payload)
69
+ return payload unless payload.is_a?(Hash)
70
+
71
+ [:data, 'data'].reduce(payload) do |result, data_key|
72
+ data = result[data_key]
73
+ next result unless data.is_a?(Hash)
74
+
75
+ result.merge(data_key => yield(data))
76
+ end
77
+ end
78
+ private_class_method :transform_data_hashes
79
+
80
+ # Secrets are keyed by name alone. They cross an ActiveJob serialization
81
+ # boundary, which coerces symbol keys to strings, so the key form at split
82
+ # time cannot be relied on to still match at merge time.
83
+ def self.secret_key_for(key)
84
+ key.to_s
85
+ end
86
+ private_class_method :secret_key_for
87
+ end
88
+ end
@@ -7,6 +7,11 @@ module Spree
7
7
  belongs_to :webhook_endpoint, class_name: 'Spree::WebhookEndpoint'
8
8
  delegate :url, to: :webhook_endpoint
9
9
 
10
+ # Credentials stripped from the persisted payload. Never written to the
11
+ # database — held in memory on a single delivery instance, assigned by
12
+ # WebhookDeliveryJob when it runs. See {Spree::WebhookPayloadRedaction}.
13
+ attr_accessor :payload_secrets
14
+
10
15
  validates :event_name, presence: true
11
16
  validates :payload, presence: true
12
17
 
@@ -64,6 +69,23 @@ module Spree
64
69
  webhook_endpoint.check_auto_disable! unless is_success
65
70
  end
66
71
 
72
+ # Payload as it should be sent over the wire.
73
+ #
74
+ # Re-attaches any credentials withheld from the persisted column. Once this
75
+ # record has been reloaded from the database those secrets are gone for
76
+ # good, so a redelivery sends the redacted placeholder — single-use tokens
77
+ # are not replayable anyway.
78
+ #
79
+ # This is deliberate: keeping a recoverable copy would put the credential
80
+ # back at rest, which is what redaction exists to prevent. If a delivery is
81
+ # lost (worker crash between creation and delivery), the customer requests
82
+ # a new reset, which mints a fresh token.
83
+ #
84
+ # @return [Hash]
85
+ def deliverable_payload
86
+ Spree::WebhookPayloadRedaction.merge(payload, payload_secrets)
87
+ end
88
+
67
89
  # Create a new delivery with the same payload and queue it.
68
90
  # Used to retry failed deliveries manually.
69
91
  #
@@ -3,6 +3,22 @@ require 'nokogiri'
3
3
  module Spree
4
4
  module DataFeeds
5
5
  class GooglePresenter < BasePresenter
6
+ # Optional Google Merchant Center product attributes sourced from
7
+ # metafields. See https://support.google.com/merchants/answer/7052112
8
+ OPTIONAL_ATTRIBUTES = %w[
9
+ brand gtin mpn identifier_exists condition adult multipack is_bundle
10
+ age_group color gender material pattern size size_type size_system
11
+ product_length product_width product_height product_weight
12
+ google_product_category product_type sale_price sale_price_effective_date
13
+ cost_of_goods_sold unit_pricing_measure unit_pricing_base_measure
14
+ shipping shipping_label shipping_weight shipping_length shipping_width
15
+ shipping_height ships_from_country transit_time_label max_handling_time
16
+ min_handling_time tax tax_category energy_efficiency_class
17
+ min_energy_efficiency_class max_energy_efficiency_class
18
+ gtin_source expiration_date custom_label_0 custom_label_1 custom_label_2
19
+ custom_label_3 custom_label_4 mobile_link additional_image_link
20
+ ].freeze
21
+
6
22
  # @return [String] RSS XML feed for Google Merchant Center
7
23
  def call
8
24
  builder = Nokogiri::XML::Builder.new do |xml|
@@ -59,10 +75,21 @@ module Spree
59
75
 
60
76
  def build_optional_attributes(xml, product)
61
77
  product.public_metafields.each do |metafield|
62
- xml['g'].send(metafield.metafield_definition.key.parameterize.underscore, metafield.value)
78
+ key = metafield.metafield_definition.key.parameterize.underscore
79
+ next unless OPTIONAL_ATTRIBUTES.include?(key)
80
+
81
+ append_g_element(xml, key, metafield.value)
63
82
  end
64
83
  end
65
84
 
85
+ def append_g_element(xml, name, value)
86
+ parent = xml.parent
87
+ node = Nokogiri::XML::Node.new(name, parent.document)
88
+ node.namespace = parent.namespace_scopes.find { |ns| ns.prefix == 'g' }
89
+ node.content = value
90
+ parent << node
91
+ end
92
+
66
93
  def format_title(product, variant)
67
94
  parts = [product.name]
68
95
  variant.option_values.each do |option_value|
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.5.3'.freeze
2
+ VERSION = '5.5.4'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.3
4
+ version: 5.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-10 00:00:00.000000000 Z
13
+ date: 2026-07-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -930,6 +930,7 @@ files:
930
930
  - app/models/concerns/spree/user_reporting.rb
931
931
  - app/models/concerns/spree/user_roles.rb
932
932
  - app/models/concerns/spree/vat_price_calculation.rb
933
+ - app/models/concerns/spree/webhook_payload_redaction.rb
933
934
  - app/models/spree/ability.rb
934
935
  - app/models/spree/address.rb
935
936
  - app/models/spree/adjustable/adjuster/base.rb
@@ -1818,9 +1819,9 @@ licenses:
1818
1819
  - BSD-3-Clause
1819
1820
  metadata:
1820
1821
  bug_tracker_uri: https://github.com/spree/spree/issues
1821
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.3
1822
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.4
1822
1823
  documentation_uri: https://docs.spreecommerce.org/
1823
- source_code_uri: https://github.com/spree/spree/tree/v5.5.3
1824
+ source_code_uri: https://github.com/spree/spree/tree/v5.5.4
1824
1825
  post_install_message:
1825
1826
  rdoc_options: []
1826
1827
  require_paths: