spree_core 5.3.4 → 5.3.5

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: 2b1ea480e8607e801d3c45d8decf522c829003eb3fa6c1f8f92b9185a4bfefec
4
- data.tar.gz: 552fb0686b6da4a560dd47144d52079d1ab73c270a9e4c9093e62b9e556ef74f
3
+ metadata.gz: 6ed5afbde4c027a14e708b2f10a4585eab43df462da64c93d409336561bd3e45
4
+ data.tar.gz: 7f10df74f02a7770d794793e396486adefcc2349c6e2f0232ed5771b6dec83b2
5
5
  SHA512:
6
- metadata.gz: 1ed2cab50fb7c122df6565b931324ea7ba307c8580a98ac8f9e2e400140df75092ed417db47311a6d3ec01985a6696cfbce1d3e3fa7917c329646b13ef51b095
7
- data.tar.gz: 34bd9929ed9e4a962696b2be41a5bb0da2c330fd12d1cbff89e34fa78cdceaa84d6b58cb7ae321d1868864c2888eccd752cd0bef7dbd9cd473aa68490619c2f6
6
+ metadata.gz: 588d726ccf12398f757ded2e252e1fb90de5a11e2c5863aa06bb27a489dd6ba3e9f1993ef68e6370b4476fa8846f59b85a60046d13953723cec4140b516128ea
7
+ data.tar.gz: 11a5f3e4298506982cecdf1dea096852e500ed157cce75343659252d3573d3e499a3d39ab82553be8e70fb7c8af414e93e016fcb8204b3c6a9cebe03521fcee7
@@ -1,12 +1,15 @@
1
1
  require 'open-uri'
2
2
  require 'openssl'
3
+ require 'ssrf_filter'
4
+ require 'tempfile'
3
5
 
4
6
  module Spree
5
7
  module Images
6
8
  class SaveFromUrlJob < ::Spree::BaseJob
7
9
  queue_as Spree.queues.images
8
- retry_on ActiveRecord::RecordInvalid, OpenURI::HTTPError, wait: :polynomially_longer, attempts: Spree::Config.images_save_from_url_job_attempts.to_i
10
+ retry_on ActiveRecord::RecordInvalid, wait: :polynomially_longer, attempts: Spree::Config.images_save_from_url_job_attempts.to_i
9
11
  discard_on URI::InvalidURIError
12
+ discard_on SsrfFilter::Error
10
13
 
11
14
  def perform(viewable_id, viewable_type, external_url, external_id = nil, position = nil)
12
15
  viewable = viewable_type.safe_constantize.find(viewable_id)
@@ -29,34 +32,55 @@ module Spree
29
32
  # still trigger save! if position has changed
30
33
  image.save! and return if image_already_saved?(image, external_url)
31
34
 
32
- uri = URI.parse(external_url)
33
- unless %w[http https].include?(uri.scheme)
34
- raise URI::InvalidURIError, "Invalid URL scheme: #{uri.scheme}. Only http and https are allowed."
35
- end
36
-
37
- file = uri.open(
38
- 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
39
- 'Accept' => 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
40
- 'Accept-Language' => 'en-US,en;q=0.9',
41
- 'Accept-Encoding' => 'gzip, deflate, br',
42
- 'Cache-Control' => 'no-cache',
43
- 'Pragma' => 'no-cache',
44
- read_timeout: 60,
45
- ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
46
- redirect: true
47
- )
48
- filename = File.basename(uri.path)
49
-
50
- image.attachment.attach(io: file, filename: filename)
51
- image.external_url = external_url
52
- image.external_id = external_id if external_id.present? && image.respond_to?(:external_id)
53
- image.save!
35
+ download_and_attach_image(external_url, image, external_id)
54
36
  rescue ActiveStorage::IntegrityError => e
55
37
  raise e unless Rails.env.test?
56
38
  end
57
39
 
58
40
  private
59
41
 
42
+ def download_and_attach_image(external_url, image, external_id)
43
+ max_size = Spree::Config.max_image_download_size
44
+
45
+ response = SsrfFilter.get(
46
+ external_url,
47
+ headers: {
48
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
49
+ 'Accept' => 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
50
+ 'Accept-Language' => 'en-US,en;q=0.9',
51
+ 'Accept-Encoding' => 'gzip, deflate, br',
52
+ 'Cache-Control' => 'no-cache',
53
+ 'Pragma' => 'no-cache'
54
+ },
55
+ http_options: {
56
+ read_timeout: 60,
57
+ open_timeout: 30
58
+ }
59
+ )
60
+
61
+ body = response.body
62
+ if body.bytesize > max_size
63
+ raise StandardError, "Image file size exceeds the maximum allowed size of #{max_size} bytes"
64
+ end
65
+
66
+ uri = URI.parse(external_url)
67
+ filename = File.basename(uri.path)
68
+ tempfile = Tempfile.new(['spree_image', File.extname(uri.path)], binmode: true)
69
+
70
+ begin
71
+ tempfile.write(body)
72
+ tempfile.rewind
73
+
74
+ image.attachment.attach(io: tempfile, filename: filename)
75
+ image.external_url = external_url
76
+ image.external_id = external_id if external_id.present? && image.respond_to?(:external_id)
77
+ image.save!
78
+ ensure
79
+ tempfile.close
80
+ tempfile.unlink
81
+ end
82
+ end
83
+
60
84
  def image_already_saved?(image, external_url)
61
85
  image.persisted? && image.attachment.attached? && image.external_url.present? && external_url == image.external_url
62
86
  end
@@ -30,6 +30,10 @@ module Spree
30
30
 
31
31
  money_methods :amount
32
32
 
33
+ def amount=(amount)
34
+ self[:amount] = Spree::LocalizedNumber.parse(amount)
35
+ end
36
+
33
37
  self.whitelisted_ransackable_attributes = %w[prefix]
34
38
 
35
39
  def generate_gift_cards
@@ -275,9 +275,9 @@ module Spree
275
275
  # @return [Boolean]
276
276
  def order_refunded?
277
277
  return false if item_count.zero?
278
+ return false if refunds_total.zero?
278
279
 
279
- (payment_state.in?(%w[void failed]) && refunds_total.positive?) ||
280
- refunds_total == total_minus_store_credits - additional_tax_total.abs
280
+ payment_state.in?(%w[void failed]) || refunds_total == total_minus_store_credits - additional_tax_total.abs
281
281
  end
282
282
 
283
283
  def refunds_total
@@ -34,6 +34,10 @@ module Spree
34
34
 
35
35
  delegate :order, :currency, to: :payment
36
36
 
37
+ def amount=(amount)
38
+ self[:amount] = Spree::LocalizedNumber.parse(amount)
39
+ end
40
+
37
41
  def money
38
42
  Spree::Money.new(amount, currency: currency)
39
43
  end
@@ -52,6 +52,10 @@ module Spree
52
52
  extend Spree::DisplayMoney
53
53
  money_methods :amount, :amount_used, :amount_remaining, :amount_authorized
54
54
 
55
+ def amount=(amount)
56
+ self[:amount] = Spree::LocalizedNumber.parse(amount)
57
+ end
58
+
55
59
  self.whitelisted_ransackable_attributes = %w[user_id created_by_id amount currency type_id]
56
60
  self.whitelisted_ransackable_associations = %w[type user created_by]
57
61
 
@@ -1,17 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ssrf_filter'
4
+ require 'resolv'
5
+
3
6
  module Spree
4
7
  class WebhookEndpoint < Spree.base_class
5
8
  acts_as_paranoid
6
9
 
7
10
  include Spree::SingleStoreResource
8
11
 
12
+ encrypts :secret_key, deterministic: true if Rails.configuration.active_record.encryption.include?(:primary_key)
13
+
9
14
  belongs_to :store, class_name: 'Spree::Store'
10
15
  has_many :webhook_deliveries, class_name: 'Spree::WebhookDelivery', dependent: :destroy_async
11
16
 
12
17
  validates :store, :url, presence: true
13
18
  validates :url, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]), message: :invalid_url }
14
19
  validates :active, inclusion: { in: [true, false] }
20
+ validate :url_must_not_resolve_to_private_ip, if: -> { url.present? && url_changed? }
15
21
 
16
22
  before_create :generate_secret_key
17
23
 
@@ -49,5 +55,16 @@ module Spree
49
55
  def generate_secret_key
50
56
  self.secret_key ||= SecureRandom.hex(32)
51
57
  end
58
+
59
+ def url_must_not_resolve_to_private_ip
60
+ uri = URI.parse(url)
61
+ blacklist = SsrfFilter::IPV4_BLACKLIST + SsrfFilter::IPV6_BLACKLIST
62
+ addresses = Resolv.getaddresses(uri.host)
63
+ if addresses.any? { |addr| blacklist.any? { |range| range.include?(IPAddr.new(addr)) } }
64
+ errors.add(:url, :internal_address_not_allowed)
65
+ end
66
+ rescue URI::InvalidURIError, Resolv::ResolvError, IPAddr::InvalidAddressError, ArgumentError
67
+ # URI format validation handles invalid URLs; DNS failures are not SSRF
68
+ end
52
69
  end
53
70
  end
@@ -41,15 +41,10 @@ module Spree
41
41
  end
42
42
 
43
43
  def get_image_link(variant, product)
44
- # try getting image from variant
45
- img = variant.images.first&.plp_url
44
+ image = variant.thumbnail || product.thumbnail
45
+ return if image.nil?
46
46
 
47
- # if no image specified for variant try getting product image
48
- if img.nil?
49
- img = product.images.first&.plp_url
50
- end
51
-
52
- img
47
+ Rails.application.routes.url_helpers.cdn_image_url(image.attachment.variant(:xlarge))
53
48
  end
54
49
 
55
50
  def format_price(variant)
@@ -25,15 +25,16 @@ module Spree
25
25
  return failure(:gift_card_mismatched_customer) if gift_card.user != order.user
26
26
  end
27
27
 
28
- amount = [gift_card.amount_remaining, order.total].min
29
28
  store = order.store
30
29
 
31
- return failure(:gift_card_no_amount_remaining) unless amount.positive? || order.total.zero?
32
-
33
30
  payment_method = ensure_store_credit_payment_method!(store)
34
31
 
35
- gift_card.lock!
36
32
  order.with_lock do
33
+ gift_card.lock!
34
+ amount = [gift_card.amount_remaining, order.total].min
35
+
36
+ return failure(:gift_card_no_amount_remaining) unless amount.positive? || order.total.zero?
37
+
37
38
  store_credit = gift_card.store_credits.create!(
38
39
  store: store,
39
40
  user: order.user,
@@ -361,6 +361,10 @@ en:
361
361
  cannot_destroy_if_attached_to_line_items: Cannot delete Variants that are added to placed Orders. In such cases, please discontinue them.
362
362
  must_supply_price_for_variant_or_master: Must supply price for variant or master price for product.
363
363
  no_master_variant_found_to_infer_price: No master variant found to infer price
364
+ spree/webhook_endpoint:
365
+ attributes:
366
+ url:
367
+ internal_address_not_allowed: must not point to an internal or private network address
364
368
  spree/wished_item:
365
369
  attributes:
366
370
  variant:
@@ -47,6 +47,7 @@ module Spree
47
47
  preference :expedited_exchanges_days_window, :integer, default: 14 # the amount of days the customer has to return their item after the expedited exchange is shipped in order to avoid being charged
48
48
  preference :geocode_addresses, :boolean, default: true
49
49
  preference :images_save_from_url_job_attempts, :integer, default: 5
50
+ preference :max_image_download_size, :integer, default: 20_971_520 # 20 MB in bytes
50
51
 
51
52
  # Preprocessed product image variant sizes at 2x retina resolution.
52
53
  # These variants are generated on upload to reduce runtime processing.
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.3.4'.freeze
2
+ VERSION = '5.3.5'.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.3.4
4
+ version: 5.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -573,6 +573,20 @@ dependencies:
573
573
  - - "~>"
574
574
  - !ruby/object:Gem::Version
575
575
  version: '2.0'
576
+ - !ruby/object:Gem::Dependency
577
+ name: ssrf_filter
578
+ requirement: !ruby/object:Gem::Requirement
579
+ requirements:
580
+ - - "~>"
581
+ - !ruby/object:Gem::Version
582
+ version: '1.0'
583
+ type: :runtime
584
+ prerelease: false
585
+ version_requirements: !ruby/object:Gem::Requirement
586
+ requirements:
587
+ - - "~>"
588
+ - !ruby/object:Gem::Version
589
+ version: '1.0'
576
590
  description: Spree Models, Helpers, Services and core libraries
577
591
  email: hello@spreecommerce.org
578
592
  executables: []
@@ -1717,9 +1731,9 @@ licenses:
1717
1731
  - BSD-3-Clause
1718
1732
  metadata:
1719
1733
  bug_tracker_uri: https://github.com/spree/spree/issues
1720
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.3.4
1734
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.3.5
1721
1735
  documentation_uri: https://docs.spreecommerce.org/
1722
- source_code_uri: https://github.com/spree/spree/tree/v5.3.4
1736
+ source_code_uri: https://github.com/spree/spree/tree/v5.3.5
1723
1737
  rdoc_options: []
1724
1738
  require_paths:
1725
1739
  - lib