Spree allows unauthenticated users can access all guest addresses
Description
Spree is an open source e-commerce solution built with Ruby on Rails. A critical IDOR vulnerability exists in Spree Commerce's guest checkout flow that allows any guest user to bind arbitrary guest addresses to their order by manipulating address ID parameters. This enables unauthorized access to other guests' personally identifiable information (PII) including names, addresses and phone numbers. The vulnerability bypasses existing ownership validation checks and affects all guest checkout transactions. This vulnerability is fixed in 4.10.3, 5.0.8, 5.1.10, 5.2.7, and 5.3.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
spree_apiRubyGems | < 4.10.3 | 4.10.3 |
spree_apiRubyGems | >= 5.0.0, < 5.0.8 | 5.0.8 |
spree_apiRubyGems | >= 5.1.0, < 5.1.10 | 5.1.10 |
spree_apiRubyGems | >= 5.2.0, < 5.2.7 | 5.2.7 |
spree_apiRubyGems | >= 5.3.0, < 5.3.2 | 5.3.2 |
Affected products
1Patches
53 files changed · +150 −6
api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 −0 added@@ -0,0 +1,78 @@ +require 'spec_helper' + +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do + let(:user) { create(:user_with_addresses) } + let(:params) { { order: order_attributes } } + let(:store) { @default_store } + + include_context 'API v2 tokens' + + describe 'checkout#update' do + subject :patch_update do + patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer + end + + let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) } + let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } } + + context 'with address attributes' do + context 'with new address attributes' do + let(:order_attributes) do + { + bill_address_attributes: { + firstname: 'John', + lastname: 'Doe', + address1: '51 Guild Street', + address2: '2nd floor', + city: 'London', + phone: '079 4721 9458', + zipcode: 'SE25 3FZ', + state_name: 'England', + country_id: create(:country).id + } + } + end + + it 'updates address from new attributes' do + expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John') + end + end + + context 'with existing address attributes' do + let(:order_attributes) do + { + bill_address_id: existing_address.id + } + end + + context 'with logged in user' do + let(:existing_address) { create(:address, user: user) } + + it 'updates address from existing address' do + expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id) + end + end + + context 'with guest user' do + let(:user) { nil } + + context 'with address that belongs to existing user' do + let(:existing_address) { create(:address, user: create(:user)) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + + context 'with address that belongs to guest user' do + let(:existing_address) { create(:address, user: nil) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + end + end + end + end +end
core/app/models/spree/order/address_book.rb+12 −4 modified@@ -19,13 +19,17 @@ def clone_billing_address end def bill_address_id=(id) + return if bill_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['bill_address_id'] = address.id - bill_address.reload else self['bill_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_bill_address end def bill_address_attributes=(attributes) @@ -34,13 +38,17 @@ def bill_address_attributes=(attributes) end def ship_address_id=(id) + return if ship_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['ship_address_id'] = address.id - ship_address.reload else self['ship_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_ship_address end def ship_address_attributes=(attributes)
core/spec/models/spree/order_spec.rb+60 −2 modified@@ -1448,7 +1448,9 @@ def create_adjustment(label, order_or_line_item, amount, source) describe "bill_address_id=" do let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } subject { order.bill_address_id = address.id } @@ -1467,6 +1469,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { bill_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.bill_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + end end describe '#bill_address_attributes=' do @@ -1513,7 +1542,9 @@ def create_adjustment(label, order_or_line_item, amount, source) describe "ship_address_id=" do let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } subject { order.ship_address_id = address.id } @@ -1532,6 +1563,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { ship_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.ship_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + end end describe '#ship_address_attributes=' do
3 files changed · +150 −6
api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 −0 added@@ -0,0 +1,78 @@ +require 'spec_helper' + +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do + let(:user) { create(:user_with_addresses) } + let(:params) { { order: order_attributes } } + let(:store) { @default_store } + + include_context 'API v2 tokens' + + describe 'checkout#update' do + subject :patch_update do + patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer + end + + let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) } + let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } } + + context 'with address attributes' do + context 'with new address attributes' do + let(:order_attributes) do + { + bill_address_attributes: { + firstname: 'John', + lastname: 'Doe', + address1: '51 Guild Street', + address2: '2nd floor', + city: 'London', + phone: '079 4721 9458', + zipcode: 'SE25 3FZ', + state_name: 'England', + country_id: create(:country).id + } + } + end + + it 'updates address from new attributes' do + expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John') + end + end + + context 'with existing address attributes' do + let(:order_attributes) do + { + bill_address_id: existing_address.id + } + end + + context 'with logged in user' do + let(:existing_address) { create(:address, user: user) } + + it 'updates address from existing address' do + expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id) + end + end + + context 'with guest user' do + let(:user) { nil } + + context 'with address that belongs to existing user' do + let(:existing_address) { create(:address, user: create(:user)) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + + context 'with address that belongs to guest user' do + let(:existing_address) { create(:address, user: nil) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + end + end + end + end +end
core/app/models/spree/order/address_book.rb+12 −4 modified@@ -14,13 +14,17 @@ def clone_billing_address end def bill_address_id=(id) + return if bill_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['bill_address_id'] = address.id - bill_address.reload else self['bill_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_bill_address end def bill_address_attributes=(attributes) @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes) end def ship_address_id=(id) + return if ship_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['ship_address_id'] = address.id - ship_address.reload else self['ship_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_ship_address end def ship_address_attributes=(attributes)
core/spec/models/spree/order_spec.rb+60 −2 modified@@ -1686,7 +1686,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.bill_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1703,6 +1705,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { bill_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.bill_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + end end describe '#bill_address_attributes=' do @@ -1762,7 +1791,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.ship_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1779,6 +1810,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { ship_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.ship_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + end end describe '#ship_address_attributes=' do
3 files changed · +150 −6
api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 −0 added@@ -0,0 +1,78 @@ +require 'spec_helper' + +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do + let(:user) { create(:user_with_addresses) } + let(:params) { { order: order_attributes } } + let(:store) { @default_store } + + include_context 'API v2 tokens' + + describe 'checkout#update' do + subject :patch_update do + patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer + end + + let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) } + let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } } + + context 'with address attributes' do + context 'with new address attributes' do + let(:order_attributes) do + { + bill_address_attributes: { + firstname: 'John', + lastname: 'Doe', + address1: '51 Guild Street', + address2: '2nd floor', + city: 'London', + phone: '079 4721 9458', + zipcode: 'SE25 3FZ', + state_name: 'England', + country_id: create(:country).id + } + } + end + + it 'updates address from new attributes' do + expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John') + end + end + + context 'with existing address attributes' do + let(:order_attributes) do + { + bill_address_id: existing_address.id + } + end + + context 'with logged in user' do + let(:existing_address) { create(:address, user: user) } + + it 'updates address from existing address' do + expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id) + end + end + + context 'with guest user' do + let(:user) { nil } + + context 'with address that belongs to existing user' do + let(:existing_address) { create(:address, user: create(:user)) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + + context 'with address that belongs to guest user' do + let(:existing_address) { create(:address, user: nil) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + end + end + end + end +end
core/app/models/spree/order/address_book.rb+12 −4 modified@@ -14,13 +14,17 @@ def clone_billing_address end def bill_address_id=(id) + return if bill_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['bill_address_id'] = address.id - bill_address.reload else self['bill_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_bill_address end def bill_address_attributes=(attributes) @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes) end def ship_address_id=(id) + return if ship_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['ship_address_id'] = address.id - ship_address.reload else self['ship_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_ship_address end def ship_address_attributes=(attributes)
core/spec/models/spree/order_spec.rb+60 −2 modified@@ -1576,7 +1576,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.bill_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1593,6 +1595,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { bill_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.bill_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + end end describe '#bill_address_attributes=' do @@ -1652,7 +1681,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.ship_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1669,6 +1700,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { ship_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.ship_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + end end describe '#ship_address_attributes=' do
3 files changed · +150 −6
api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 −0 added@@ -0,0 +1,78 @@ +require 'spec_helper' + +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do + let(:user) { create(:user_with_addresses) } + let(:params) { { order: order_attributes } } + let(:store) { @default_store } + + include_context 'API v2 tokens' + + describe 'checkout#update' do + subject :patch_update do + patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer + end + + let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) } + let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } } + + context 'with address attributes' do + context 'with new address attributes' do + let(:order_attributes) do + { + bill_address_attributes: { + firstname: 'John', + lastname: 'Doe', + address1: '51 Guild Street', + address2: '2nd floor', + city: 'London', + phone: '079 4721 9458', + zipcode: 'SE25 3FZ', + state_name: 'England', + country_id: create(:country).id + } + } + end + + it 'updates address from new attributes' do + expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John') + end + end + + context 'with existing address attributes' do + let(:order_attributes) do + { + bill_address_id: existing_address.id + } + end + + context 'with logged in user' do + let(:existing_address) { create(:address, user: user) } + + it 'updates address from existing address' do + expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id) + end + end + + context 'with guest user' do + let(:user) { nil } + + context 'with address that belongs to existing user' do + let(:existing_address) { create(:address, user: create(:user)) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + + context 'with address that belongs to guest user' do + let(:existing_address) { create(:address, user: nil) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + end + end + end + end +end
core/app/models/spree/order/address_book.rb+12 −4 modified@@ -14,13 +14,17 @@ def clone_billing_address end def bill_address_id=(id) + return if bill_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['bill_address_id'] = address.id - bill_address.reload else self['bill_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_bill_address end def bill_address_attributes=(attributes) @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes) end def ship_address_id=(id) + return if ship_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['ship_address_id'] = address.id - ship_address.reload else self['ship_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_ship_address end def ship_address_attributes=(attributes)
core/spec/models/spree/order_spec.rb+60 −2 modified@@ -1866,7 +1866,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.bill_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1883,6 +1885,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { bill_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.bill_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + end end describe '#bill_address_attributes=' do @@ -1942,7 +1971,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.ship_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1959,6 +1990,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { ship_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.ship_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + end end describe '#ship_address_attributes=' do
3 files changed · +150 −6
api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 −0 added@@ -0,0 +1,78 @@ +require 'spec_helper' + +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do + let(:user) { create(:user_with_addresses) } + let(:params) { { order: order_attributes } } + let(:store) { @default_store } + + include_context 'API v2 tokens' + + describe 'checkout#update' do + subject :patch_update do + patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer + end + + let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) } + let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } } + + context 'with address attributes' do + context 'with new address attributes' do + let(:order_attributes) do + { + bill_address_attributes: { + firstname: 'John', + lastname: 'Doe', + address1: '51 Guild Street', + address2: '2nd floor', + city: 'London', + phone: '079 4721 9458', + zipcode: 'SE25 3FZ', + state_name: 'England', + country_id: create(:country).id + } + } + end + + it 'updates address from new attributes' do + expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John') + end + end + + context 'with existing address attributes' do + let(:order_attributes) do + { + bill_address_id: existing_address.id + } + end + + context 'with logged in user' do + let(:existing_address) { create(:address, user: user) } + + it 'updates address from existing address' do + expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id) + end + end + + context 'with guest user' do + let(:user) { nil } + + context 'with address that belongs to existing user' do + let(:existing_address) { create(:address, user: create(:user)) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + + context 'with address that belongs to guest user' do + let(:existing_address) { create(:address, user: nil) } + + it 'does not update guest user address' do + expect { patch_update }.not_to change { order.reload.bill_address_id } + end + end + end + end + end + end +end
core/app/models/spree/order/address_book.rb+12 −4 modified@@ -14,13 +14,17 @@ def clone_billing_address end def bill_address_id=(id) + return if bill_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['bill_address_id'] = address.id - bill_address.reload else self['bill_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_bill_address end def bill_address_attributes=(attributes) @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes) end def ship_address_id=(id) + return if ship_address_id == id + address = Spree::Address.find_by(id: id) - if address && address.user_id == user_id + # rubocop:disable Style/ConditionalAssignment + if address && user_id.present? && address.user_id == user_id self['ship_address_id'] = address.id - ship_address.reload else self['ship_address_id'] = nil end + # rubocop:enable Style/ConditionalAssignment + reset_ship_address end def ship_address_attributes=(attributes)
core/spec/models/spree/order_spec.rb+60 −2 modified@@ -1668,7 +1668,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.bill_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1685,6 +1687,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { bill_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.bill_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order bill address to nil' do + expect { subject }.to change { order.bill_address_id }.to(nil) + end + end + end end describe '#bill_address_attributes=' do @@ -1744,7 +1773,9 @@ def create_adjustment(label, order_or_line_item, amount, source) subject { order.ship_address_id = address.id } let(:user) { create(:user) } - let(:order) { create(:order, user: user) } + let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) } + let(:bill_address) { create(:address, user: user) } + let(:ship_address) { create(:address, user: user) } let(:address) { create(:address, user: user) } context 'when assigned address exist' do @@ -1761,6 +1792,33 @@ def create_adjustment(label, order_or_line_item, amount, source) end end end + + context 'when assigned address does not belong to user' do + let(:address) { create(:address, user: create(:user)) } + + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + + context 'with guest user' do + let(:user) { nil } + let(:address) { create(:address, user: nil) } + + context 'when assigning the same existing address' do + let(:address) { ship_address } + + it 'does nothing' do + expect { subject }.not_to(change { order.ship_address_id }) + end + end + + context 'when assigning a different existing address' do + it 'sets order ship address to nil' do + expect { subject }.to change { order.ship_address_id }.to(nil) + end + end + end end describe '#ship_address_attributes=' do
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
13- github.com/advisories/GHSA-87fh-rc96-6fr6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25758ghsaADVISORY
- github.com/rubysec/ruby-advisory-db/blob/master/gems/spree_api/CVE-2026-25758.ymlghsaWEB
- github.com/spree/spree/blob/1341623f2ae92685cdbe232885bf5808fc8f9ca8/core/app/models/spree/order/address_book.rbghsax_refsource_MISCWEB
- github.com/spree/spree/blob/1341623f2ae92685cdbe232885bf5808fc8f9ca8/core/app/models/spree/order/checkout.rbghsax_refsource_MISCWEB
- github.com/spree/spree/blob/1341623f2ae92685cdbe232885bf5808fc8f9ca8/core/app/services/spree/checkout/update.rbghsax_refsource_MISCWEB
- github.com/spree/spree/blob/1341623f2ae92685cdbe232885bf5808fc8f9ca8/core/lib/spree/permitted_attributes.rbghsax_refsource_MISCWEB
- github.com/spree/spree/commit/15619618e43b367617ec8d2d4aafc5e54fa7b734ghsax_refsource_MISCWEB
- github.com/spree/spree/commit/29282d1565ba4f7bc2bbc47d550e2c0c6d0ae59fghsax_refsource_MISCWEB
- github.com/spree/spree/commit/6650f96356faa0d16c05bcb516f1ffd5641741b8ghsax_refsource_MISCWEB
- github.com/spree/spree/commit/902d301ac83fd2047db1b9a3a99545162860f748ghsax_refsource_MISCWEB
- github.com/spree/spree/commit/ff7cfcfcfe0c40c60d03317e1d0ee361c6a6b054ghsax_refsource_MISCWEB
- github.com/spree/spree/security/advisories/GHSA-87fh-rc96-6fr6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.