spree_core 5.3.3 → 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 +4 -4
- data/app/jobs/spree/images/save_from_url_job.rb +47 -23
- data/app/models/spree/gift_card_batch.rb +4 -0
- data/app/models/spree/order.rb +2 -2
- data/app/models/spree/product/slugs.rb +1 -1
- data/app/models/spree/refund.rb +4 -0
- data/app/models/spree/store_credit.rb +4 -0
- data/app/models/spree/variant.rb +19 -17
- data/app/models/spree/webhook_endpoint.rb +17 -0
- data/app/services/spree/data_feeds/google/required_attributes.rb +3 -8
- data/app/services/spree/data_feeds/google/rss.rb +3 -1
- data/app/services/spree/gift_cards/apply.rb +5 -4
- data/app/views/spree/addresses/_form.html.erb +1 -2
- data/config/locales/en.yml +4 -0
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ed5afbde4c027a14e708b2f10a4585eab43df462da64c93d409336561bd3e45
|
|
4
|
+
data.tar.gz: 7f10df74f02a7770d794793e396486adefcc2349c6e2f0232ed5771b6dec83b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
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
|
-
|
|
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
|
data/app/models/spree/order.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -103,7 +103,7 @@ module Spree
|
|
|
103
103
|
translations.with_deleted.each { |rec| rec.update_columns(slug: new_slug.call(rec)) }
|
|
104
104
|
slugs.with_deleted.each { |rec| rec.update_column(:slug, new_slug.call(rec)) }
|
|
105
105
|
|
|
106
|
-
translations.find_by
|
|
106
|
+
translations.find_by(locale: I18n.locale)&.update_column(:slug, slug) if Spree.use_translations?
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
end
|
data/app/models/spree/refund.rb
CHANGED
|
@@ -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
|
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -91,36 +91,33 @@ module Spree
|
|
|
91
91
|
scope :backorderable, -> { left_joins(:stock_items).where(spree_stock_items: { backorderable: true }) }
|
|
92
92
|
scope :in_stock_or_backorderable, -> { in_stock.or(backorderable) }
|
|
93
93
|
|
|
94
|
-
scope :eligible,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
select(:product_id).
|
|
99
|
-
group(:product_id).
|
|
100
|
-
having("COUNT(#{Spree::Variant.table_name}.id) = 1")
|
|
94
|
+
scope :eligible, lambda {
|
|
95
|
+
joins(:product).where(
|
|
96
|
+
arel_table[:is_master].eq(false).or(
|
|
97
|
+
Spree::Product.arel_table[:variant_count].eq(0)
|
|
101
98
|
)
|
|
102
99
|
)
|
|
103
100
|
}
|
|
104
101
|
|
|
105
|
-
scope :not_discontinued,
|
|
102
|
+
scope :not_discontinued, lambda {
|
|
106
103
|
where(
|
|
107
104
|
arel_table[:discontinue_on].eq(nil).or(
|
|
108
105
|
arel_table[:discontinue_on].gteq(Time.current)
|
|
109
106
|
)
|
|
110
107
|
)
|
|
111
|
-
|
|
108
|
+
}
|
|
112
109
|
|
|
113
110
|
scope :not_deleted, -> { where("#{Spree::Variant.quoted_table_name}.deleted_at IS NULL") }
|
|
114
111
|
|
|
115
|
-
scope :for_currency_and_available_price_amount,
|
|
112
|
+
scope :for_currency_and_available_price_amount, lambda { |currency = nil|
|
|
116
113
|
currency ||= Spree::Store.default.default_currency
|
|
117
114
|
joins(:prices).where("#{Spree::Price.table_name}.currency = ?", currency).where("#{Spree::Price.table_name}.amount IS NOT NULL").distinct
|
|
118
|
-
|
|
115
|
+
}
|
|
119
116
|
|
|
120
|
-
scope :active,
|
|
117
|
+
scope :active, lambda { |currency = nil|
|
|
121
118
|
not_discontinued.not_deleted.
|
|
122
119
|
for_currency_and_available_price_amount(currency)
|
|
123
|
-
|
|
120
|
+
}
|
|
124
121
|
|
|
125
122
|
scope :with_option_value, lambda { |option_name, option_value|
|
|
126
123
|
option_type_ids = OptionType.where(name: option_name).ids
|
|
@@ -187,7 +184,8 @@ module Spree
|
|
|
187
184
|
)
|
|
188
185
|
|
|
189
186
|
self.whitelisted_ransackable_associations = %w[option_values product tax_category prices default_price]
|
|
190
|
-
self.whitelisted_ransackable_attributes = %w[weight depth width height sku discontinue_on is_master cost_price cost_currency track_inventory
|
|
187
|
+
self.whitelisted_ransackable_attributes = %w[weight depth width height sku discontinue_on is_master cost_price cost_currency track_inventory
|
|
188
|
+
deleted_at]
|
|
191
189
|
self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku)
|
|
192
190
|
|
|
193
191
|
def self.product_name_or_sku_cont(query)
|
|
@@ -258,9 +256,13 @@ module Spree
|
|
|
258
256
|
# @return [String] the options text of the variant
|
|
259
257
|
def options_text
|
|
260
258
|
@options_text ||= if option_values.loaded?
|
|
261
|
-
option_values.sort_by
|
|
259
|
+
option_values.sort_by do |ov|
|
|
260
|
+
ov.option_type.position
|
|
261
|
+
end.map { |ov| "#{ov.option_type.presentation}: #{ov.presentation}" }.to_sentence(words_connector: ', ', two_words_connector: ', ')
|
|
262
262
|
else
|
|
263
|
-
option_values.includes(:option_type).joins(:option_type).order("#{Spree::OptionType.table_name}.position").map
|
|
263
|
+
option_values.includes(:option_type).joins(:option_type).order("#{Spree::OptionType.table_name}.position").map do |ov|
|
|
264
|
+
"#{ov.option_type.presentation}: #{ov.presentation}"
|
|
265
|
+
end.to_sentence(words_connector: ', ', two_words_connector: ', ')
|
|
264
266
|
end
|
|
265
267
|
end
|
|
266
268
|
|
|
@@ -273,7 +275,7 @@ module Spree
|
|
|
273
275
|
# Returns the descriptive name of the variant.
|
|
274
276
|
# @return [String] the descriptive name of the variant
|
|
275
277
|
def descriptive_name
|
|
276
|
-
is_master? ? name
|
|
278
|
+
is_master? ? "#{name} - Master" : "#{name} - #{options_text}"
|
|
277
279
|
end
|
|
278
280
|
|
|
279
281
|
# use deleted? rather than checking the attribute directly. this
|
|
@@ -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
|
-
|
|
45
|
-
|
|
44
|
+
image = variant.thumbnail || product.thumbnail
|
|
45
|
+
return if image.nil?
|
|
46
46
|
|
|
47
|
-
|
|
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)
|
|
@@ -18,7 +18,9 @@ module Spree
|
|
|
18
18
|
result = products_list.call(store)
|
|
19
19
|
if result.success?
|
|
20
20
|
result.value[:products].find_each do |product|
|
|
21
|
-
product.
|
|
21
|
+
product.variants_including_master.active.find_each do |variant|
|
|
22
|
+
next if variant.is_master? && product.has_variants?
|
|
23
|
+
|
|
22
24
|
add_variant_information_to_xml(xml, product, variant)
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -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,
|
|
@@ -45,8 +45,7 @@
|
|
|
45
45
|
</div>
|
|
46
46
|
<%= render "spree/addresses/suggestions_box" %>
|
|
47
47
|
</div>
|
|
48
|
-
<span class="text-sm space-x-2
|
|
49
|
-
<%= heroicon "exclamation-circle", variant: :outline if defined?(heroicon) %>
|
|
48
|
+
<span class="text-sm space-x-2 hidden alert alert-info mt-2 static" data-address-autocomplete-target="addressWarning">
|
|
50
49
|
<%= Spree.t('address_book.add_house_number') %>
|
|
51
50
|
</span>
|
|
52
51
|
</div>
|
data/config/locales/en.yml
CHANGED
|
@@ -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.
|
data/lib/spree/core/version.rb
CHANGED
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
|
+
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.
|
|
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.
|
|
1736
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.3.5
|
|
1723
1737
|
rdoc_options: []
|
|
1724
1738
|
require_paths:
|
|
1725
1739
|
- lib
|