CVE-2024-29888 (Python, published 2024-03-27T19:15Z)
Description: value: Saleor is an e-commerce platform that serves high-volume companies. When using `Pickup: Local stock only` click-and-collect as a delivery method in specific conditions the customer could overwrite the warehouse address with its own, which exposes its address as click-and-collect address. This issue has been patched in versions: `3.14.61`, `3.15.37`, `3.16.34`, `3.17.32`, `3.18.28`, `3.19.15`.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/saleor/saleor
Files changed:
saleor/checkout/complete_checkout.py
saleor/graphql/checkout/tests/mutations/test_checkout_complete_with_payment.py
saleor/graphql/checkout/tests/mutations/test_checkout_complete_with_transactions.py
saleor/order/migrations/0172_update_order_cc_addresses.py
saleor/order/migrations/0176_merge_20240325_1315.py
saleor/order/migrations/0177_merge_20240325_1329.py
saleor/order/migrations/0180_merge_20240325_1333.py
saleor/order/migrations/0182_merge_20240325_1338.py
saleor/order/migrations/0184_merge_0182_merge_20240325_1338_0183_order_tax_error.py
saleor/order/migrations/tasks/saleor3_19.py
Commit message: Fix relation between order and click and collect address (#15697)

* Create a copy of collection point address during checkout completion

* Add migration for creating order shipping addresses

Vulnerable code:
    checkout.shipping_address = None
    assert order.shipping_address == warehouse_for_cc.address
    assert order.shipping_address == warehouse_for_cc.address
    assert order.shipping_address == warehouse_for_cc.address
    assert order.shipping_address == warehouse_for_cc.address

Fixed code:
    if shipping_address and delivery_method_info.warehouse_pk:
        shipping_address = shipping_address.get_copy()

    checkout.shipping_address = warehouse_for_cc.address
    assert order.shipping_address
    assert order.shipping_address.id != warehouse_for_cc.address.id
    assert order.shipping_address
    assert order.shipping_address.id != warehouse_for_cc.address.id
    assert order.shipping_address
    assert order.shipping_address.id != warehouse_for_cc.address.id
    assert order.shipping_address
    assert order.shipping_address.id != warehouse_for_cc.address.id
from django.db import migrations
from django.db.models import Exists, OuterRef
from django.forms.models import model_to_dict

from .tasks.saleor3_19 import update_order_addresses_task

# The batch of size 250 takes ~0.5 second and consumes ~20MB memory at peak
ADDRESS_UPDATE_BATCH_SIZE = 250


def update_order_addresses(apps, schema_editor):
    Order = apps.get_model("order", "Order")
    Warehouse = apps.get_model("warehouse", "Warehouse")
    Address = apps.get_model("account", "Address")
    qs = Order.objects.filter(
        Exists(Warehouse.objects.filter(address_id=OuterRef("shipping_address_id"))),
    )
    order_ids = qs.values_list("pk", flat=True)[:ADDRESS_UPDATE_BATCH_SIZE]
    addresses = []
    if order_ids:
        orders = Order.objects.filter(id__in=order_ids)
        for order in orders:
            if cc_address := order.shipping_address:
                order_address = Address(**model_to_dict(cc_address, exclude=["id"]))
                order.shipping_address = order_address
                addresses.append(order_address)
        Address.objects.bulk_create(addresses, ignore_conflicts=True)
        Order.objects.bulk_update(orders, ["shipping_address"])
        update_order_addresses_task.delay()


class Migration(migrations.Migration):
    dependencies = [
        ("order", "0171_order_order_user_email_user_id_idx"),
    ]

    operations = [
        migrations.RunPython(update_order_addresses, migrations.RunPython.noop),
    ]
# Generated by Django 3.2.22 on 2024-03-25 13:15

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("order", "0172_update_order_cc_addresses"),
        ("order", "0175_merge_20231122_1040"),
    ]

    operations = []
# Generated by Django 3.2.22 on 2024-03-25 13:29

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("order", "0176_merge_20231122_1346"),
        ("order", "0176_merge_20240325_1315"),
    ]

    operations = []
# Generated by Django 3.2.22 on 2024-03-25 13:33

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("order", "0177_merge_20240325_1329"),
        ("order", "0179_merge_20231122_1348"),
    ]

    operations = []
# Generated by Django 3.2.22 on 2024-03-25 13:38

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("order", "0180_merge_20240325_1333"),
        ("order", "0181_order_subtotal_as_a_field"),
    ]

    operations = []
# Generated by Django 3.2.22 on 2024-03-25 13:42

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("order", "0182_merge_20240325_1338"),
        ("order", "0183_order_tax_error"),
    ]

    operations = []
from django.db.models import Exists, OuterRef
from django.forms.models import model_to_dict

from ....account.models import Address
from ....celeryconf import app
from ....warehouse.models import Warehouse
from ...models import Order

# The batch of size 250 takes ~0.5 second and consumes ~20MB memory at peak
ADDRESS_UPDATE_BATCH_SIZE = 250


@app.task
def update_order_addresses_task():
    qs = Order.objects.filter(
        Exists(Warehouse.objects.filter(address_id=OuterRef("shipping_address_id"))),
    )
    order_ids = qs.values_list("pk", flat=True)[:ADDRESS_UPDATE_BATCH_SIZE]
    addresses = []
    if order_ids:
        orders = Order.objects.filter(id__in=order_ids)
        for order in orders:
            if cc_address := order.shipping_address:
                order_address = Address(**model_to_dict(cc_address, exclude=["id"]))
                order.shipping_address = order_address
                addresses.append(order_address)
        Address.objects.bulk_create(addresses, ignore_conflicts=True)
        Order.objects.bulk_update(orders, ["shipping_address"])
        update_order_addresses_task.delay()