addons-cm/docs/openspec/changes/security-audit-hardening/specs/eskaera-access-control/spec.md
2026-08-07 16:46:33 +02:00

4.7 KiB

ADDED Requirements

Requirement: Least-privilege ACLs for group order models

group.order and group.order.slot SHALL NOT grant write, create or unlink to portal, public or unassigned internal users. Write/create/unlink SHALL be limited to the group-order manager group (website_sale_aplicoop.group_group_order_manager) and ERP admins. Read MAY be granted to internal users and portal users through explicit, group-scoped ACLs (no empty group_id rows). Controller mutations continue to run via sudo() and MUST NOT depend on broad ACLs.

Scenario: Portal user cannot mutate group orders

  • WHEN a portal user calls the external API to create, write or unlink on group.order or group.order.slot
  • THEN the operation is denied with an AccessError

Scenario: Manager retains full access

  • WHEN a user in group_group_order_manager creates or edits a group.order of their company
  • THEN the operation succeeds

Scenario: Controllers still operate

  • WHEN an authenticated portal member confirms a cart through the eskaera controller (which uses sudo())
  • THEN the underlying sale.order/group.order writes succeed despite the tighter ACLs

Requirement: Supplier cost not broadly exposed to portal

Portal users SHALL NOT read product.supplierinfo cost/price rows for products or vendors unrelated to them. The unrestricted record rule (domain=[(1,'=',1)]) granting portal read on product.supplierinfo SHALL NOT exist. The product origin and main seller shown in the eskaera shop — currently painted from supplierinfo — MUST keep working, either by preparing those values server-side (via sudo() in the controller, per the repo's "no logic in QWeb" rule) or via a record rule scoped to the products the user can access, exposing no supplier cost/price fields.

Scenario: Portal user cannot list unrelated supplier cost

  • WHEN a portal user calls search_read on product.supplierinfo via the external API
  • THEN the result does not include supplier cost/price rows of products/vendors unrelated to the user

Scenario: Origin and main seller still render

  • WHEN a portal member opens the eskaera shop
  • THEN each product's origin and main seller display correctly, without exposing supplier cost/price across all vendors

Requirement: CSRF protection on state-changing eskaera endpoints

State-changing eskaera endpoints SHALL be protected against cross-site request forgery. The endpoints /eskaera/save-order, /eskaera/confirm, /eskaera/clear-cart and /eskaera/save-cart (which create, confirm, merge or cancel sale.order records) SHALL NOT be type="http" with csrf=False; they SHALL be type="json" (or otherwise validate a CSRF token) so that a cross-site HTML form POST cannot trigger them.

Scenario: Cross-site form POST is rejected

  • WHEN a logged-in user's browser is induced to submit a cross-site form POST to /eskaera/clear-cart or /eskaera/confirm
  • THEN the request is rejected and no order is cancelled/confirmed

Scenario: Legitimate same-origin call works

  • WHEN the eskaera frontend submits the JSON-RPC request from the same origin
  • THEN the order is created/confirmed as before

Requirement: Consumer-group membership enforced on group-order endpoints

Every eskaera endpoint that resolves a group.order from a request id SHALL verify that the requesting user belongs to a consumer group of that order before returning data. This applies to load_eskaera_page, load_products_ajax, add_to_eskaera_cart, eskaera_checkout and check_group_order_status, reusing _validate_user_group_access / _get_consumer_group_for_user. Internal users MAY be exempt (matching eskaera_shop).

Scenario: Non-member gets no cross-group data

  • WHEN a portal user who is not a member of a group order requests /eskaera/<order_id>/load-products-ajax for that order
  • THEN the endpoint returns empty/redirect and discloses no catalog or pricing of that order

Scenario: Member accesses own group order

  • WHEN a portal member of the order's consumer group requests the same endpoint
  • THEN the products and pricing are returned normally

Requirement: Safe template output in inline scripts

QWeb templates SHALL NOT inject data into inline <script> blocks via t-raw. JSON payloads for the client SHALL be emitted through a safe pattern (e.g. <script type="application/json"> read by the JS layer).

Scenario: Rendered history payload is not raw-injected

  • WHEN the load-from-history template renders its JSON payload
  • THEN the payload is emitted through a safe (escaped/typed) mechanism, not t-raw inside a <script> tag