You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
the id > last_record_id cursor combined with an arbitrary sort skips rows
prev links on consent-filtered pages are approximate (offset - count doesn't invert the scanned offset).
GET /Program pages with _lastId, a raw database id (against api-design.md), and its total is counted after
the cursor, so it shrinks from page to page. The Program list also includes archived programs that GET /Program/{id} can't read.
odoo.http logs every handled HTTPException (any 4xx) at ERROR ("Exception during request handling"):
144 such lines in the spp_api_v2 suite, and the same noise in production logs.
Identifier values (e.g. national IDs) in INFO logs:
409/404 detection in the member endpoints matches English substrings of translated messages ("already a member",
"not a member"). A translation would turn them into 422. Use dedicated exception classes.
group=none loads every active membership into an id not in [...] list on each batch. Use a not any domain.
A savepoint rollback clears the cursor's precommit hooks (Odoo 19 _FlushingSavepoint). No impact today (no
precommit use in spp_api_v2*); note it for when audit logging moves to precommit.
Search timing: a hidden match costs serialization + consent queries per row, an unknown identifier costs nothing;
the search path has no jitter (the read path does). Run the cheap consent check before to_api_schema.
routers/group.py:
membership-history self/next/prev links (:1012) and the Location headers (:332, :949) carry a raw #
membership-history _offset (:966) has no le=MAX_OFFSET (huge value gives a 500)
Role lookups accept a code from any vocabulary ($add-member with urn:iso:std:iso:5218|1 links "Male" as a role).
Restricting to urn:openspp:vocab:group-membership-type needs an owner decision; the existing tests use a test
vocabulary.
Read endpoints decide consent/jitter from is_require_consent, while search and filter_response use legal_basis. A client with legal_basis="consent" and is_require_consent=False is consent-filtered in search
but unchecked on read.
Three copies of the non-consent legal-basis list:
ConsentService.NON_CONSENT_BASES
AuthenticatedClient.has_legal_basis_bypass
spp_dci_server/services/consent_adapter.py
$add-member and $split catch-alls still return str(e) when the text contains "already a member", "not a
member" or "head".
Nits:
the gender message is an untranslated f-string
MEMBERSHIP_UNIQUE_CONSTRAINT could be derived from _table
Items
POST /Individual/_search(routers/filter.py:228) has the paging defects fix(api_v2): REST API v2 defects found by the OpenFn adaptor (#554) #555 fixed in the GET searches:nextis dropped on a short pageid > last_record_idcursor combined with an arbitrarysortskips rowsprevlinks on consent-filtered pages are approximate (offset - countdoesn't invert the scanned offset).GET /Programpages with_lastId, a raw database id (against api-design.md), and itstotalis counted afterthe cursor, so it shrinks from page to page. The Program list also includes archived programs that
GET /Program/{id}can't read.After fix(api_v2): REST API v2 defects found by the OpenFn adaptor (#554) #555, D10 lets a consent client with
_count=1over many hidden rows follow many empty pages (the 3×countscan cap). Consider scanning up to the per-query maximum on short consent pages.
D10 signal: walking
nextoffsets on a consent-filtered search reveals how many hidden rows match a filter(final offset − visible). It is accepted under D10; revisit with security(spp_api_v2): "exists but no consent" 403 has no timing jitter, unlike "not found" [Severity: Low] #559/security(spp_api_v2_programs): POST /ProgramMembership "Beneficiary not found: <ref>" 422 reveals registrant existence [Severity: Low] #560.
odoo.httplogs every handledHTTPException(any 4xx) at ERROR ("Exception during request handling"):144 such lines in the
spp_api_v2suite, and the same noise in production logs.Identifier values (e.g. national IDs) in INFO logs:
group_serviceadd/update/remove memberSame class as spp_api_v2: GroupService._create_members logs identifier values (PII in logs) #566.
Unbounded deep
_offsetscans on the searches (bounded to bigint in fix(api_v2): REST API v2 defects found by the OpenFn adaptor (#554) #555, but not for cost).409/404 detection in the member endpoints matches English substrings of translated messages ("already a member",
"not a member"). A translation would turn them into 422. Use dedicated exception classes.
group=noneloads every active membership into anid not in [...]list on each batch. Use anot anydomain.A savepoint rollback clears the cursor's precommit hooks (Odoo 19
_FlushingSavepoint). No impact today (noprecommit use in
spp_api_v2*); note it for when audit logging moves to precommit.spp_api_v2_change_request:routers/change_request.py:145returns{str(e)}in a 422 (the leak class fix(api_v2): REST API v2 defects found by the OpenFn adaptor (#554) #555 fixed in ProgramMembership)services/change_request_service.py:357looks up vocabulary codes without sudo (the fix(api_v2): REST API v2 defects found by the OpenFn adaptor (#554) #555 PATCH-gender bug class)Search timing: a hidden match costs serialization + consent queries per row, an unknown identifier costs nothing;
the search path has no jitter (the read path does). Run the cheap consent check before
to_api_schema.routers/group.py:self/next/prevlinks (:1012) and the Location headers (:332,:949) carry a raw#_offset(:966) has nole=MAX_OFFSET(huge value gives a 500)Role lookups accept a code from any vocabulary (
$add-memberwithurn:iso:std:iso:5218|1links "Male" as a role).Restricting to
urn:openspp:vocab:group-membership-typeneeds an owner decision; the existing tests use a testvocabulary.
Read endpoints decide consent/jitter from
is_require_consent, while search andfilter_responseuselegal_basis. A client withlegal_basis="consent"andis_require_consent=Falseis consent-filtered in searchbut unchecked on read.
Three copies of the non-consent legal-basis list:
ConsentService.NON_CONSENT_BASESAuthenticatedClient.has_legal_basis_bypassspp_dci_server/services/consent_adapter.py$add-memberand$splitcatch-alls still returnstr(e)when the text contains "already a member", "not amember" or "head".
Nits:
MEMBERSHIP_UNIQUE_CONSTRAINTcould be derived from_table?gender=search filter accepts any vocabulary (harmless since fix(api_v2): REST API v2 defects found by the OpenFn adaptor (#554) #555)Found while checking OpenSPP2 REST API v2 against the OpenFn
@openfn/language-opensppv4 adaptor (#554, PR #555); not fixed in #555.