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

87 lines
4.7 KiB
Markdown

## 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