Conversation
|
Reading API doc, I realized that => Fixed in "Bug 2065171 - Fix ids/include_fields precedence: query string wins over body" |
`_request_params->{ids} // []` made a missing ids param filter to nothing instead of returning every visited bug, since an empty arrayref is truthy. Legacy left $ids undef when the param is absent, skipping filter entirely. Return undef in that case matches legacy behavior. An empty array still filters to nothing.
_request_params duplicated the query-string/JSON-body merge logic. Now call a single shared Bugzilla::WebService::Util::merge_request_params helper, so it's a one-place change to drop later if query-string-on-POST support is ever removed.
|
Pushed a follow-up commit: |
_ids_from_request short-circuited to the path id whenever present, never consulting the merged
query-string/body params. Legacy's _retrieve_json_params merges non-GET request-body/query
params in *after* the path-derived params, so those win for POST. For GET, the path id still wins
(legacy's override step only ran for non-GET requests), so that precedence is unchanged.
Also switch from $self->param('id') (a truthiness check that also falls back to a same-named
query param) to $self->stash('id') (defined check, route-placeholder only). This fixes two more bugs:
- /bug_user_last_visit/0 was falling through to the no-ids branch since "0" is falsy
- a stray ?id=5 query parameter (distinct from ids) was being treated as if it were a path id
Covers: anonymous access requiring login, OPTIONS, POST via path id, POST via a JSON ids body, POST with a JSON body posted with no Content-Type header, GET via path id vs query-string ids precedence, GET via query-string ids, and GET with no ids returning every visited bug
| if (length $c->req->body) { | ||
| my $body_params; | ||
| try { $body_params = decode_json($c->req->body); } | ||
| catch { $body_params = undef; }; | ||
| $params = {%$body_params, %$params} if ref $body_params eq 'HASH'; | ||
| } |
There was a problem hiding this comment.
a malformed JSON body is silently discarded here, which can produce a wrong result instead of an error
legacy _retrieve_json_params threw json_rpc_invalid_params on a decode failure. now the catch sets undef and the request continues.
concrete case: a client sends POST /rest/bug_user_last_visit/123 with an intended body of {"ids":[456]}, but the body is truncated. decode fails, $body_params is undef, and the path id wins, so the endpoint returns 200 and records a visit against bug 123 instead of 456. no error reaches the caller.
the bare POST /rest/bug_user_last_visit variant degrades into a confusing param_required for the same reason.
suggestion: when the body is non-empty and the content type is JSON but decoding fails, raise rest_malformed_json (already used in Bugzilla/API/V1/Reminders.pm:69). keep ignoring genuinely non-JSON bodies so urlencoded and multipart form posts still work.
worth also calling this file out in the PR description, since it is shared and the change affects more than this endpoint
| my $bug_id_1 = create_bug('bug_user_last_visit test bug 1'); | ||
| my $bug_id_2 = create_bug('bug_user_last_visit test bug 2'); | ||
|
|
||
| ### Section 1: Anonymous access requires login |
There was a problem hiding this comment.
the negative cases that would actually prove behavioral parity are missing
- a group-restricted bug the user cannot see - this is the security relevant one, and the parity claim in the PR description rests on it.
qa/configalready has restricted-group fixtures used by otherqa/t/rest_*.tfiles - anonymous
POST(only anonymous GET is covered) - a nonexistent bug id, including that the transaction is rolled back mid-loop
POST /rest/bug_user_last_visitwith noidsanywhere, expectingparam_requiredinclude_fields/exclude_fields, which_request_paramsexplicitly handles
the first one plus the malformed-body case on Util.pm are the same underlying gap: failures that quietly turn into plausible-looking successes
| @last_visits = grep { $id_set{$_->bug_id} } @last_visits; | ||
| } | ||
|
|
||
| my $params = $self->_request_params; |
There was a problem hiding this comment.
the request body gets read four times per POST on a hot path
_request_params runs twice per request, once inside _ids_from_request (line 47 in get, line 84 in update) and again here, and merge_request_params itself calls $c->req->body twice (Bugzilla/WebService/Util.pm:316,318). for a file-backed request asset each body call re-slurps from disk, so one POST slurps 4x and JSON-decodes 2x.
extensions/BugModal/web/bug_modal.js:1699 fires a GET plus a POST on every bug page view, so this runs constantly.
suggestion: compute my $params = $self->_request_params once in get and update and pass it into _ids_from_request, and hoist my $body = $c->req->body into a variable in merge_request_params
| foreach my $path ('/', '/:id') { | ||
| $routes->options($path)->to('V1::BugUserLastVisit#options'); | ||
| } |
There was a problem hiding this comment.
the OPTIONS routes use a bare /:id while GET and POST on lines 23 and 25 constrain [id => qr/\d+/]
so OPTIONS /rest/bug_user_last_visit/abc answers 200 Allow: GET, POST, advertising methods that 404 on that path
suggestion: apply the same [id => qr/\d+/] constraint in the OPTIONS loop
Summary
Ports
Bugzilla::WebService::BugUserLastVisit'sget/updatemethods into a nativeBugzilla::API::V1::BugUserLastVisitMojo controller, mirroring the pattern already used for Classification/Component/Teams/Reminders/Configuration/Bugzilla (system info).This is a child bug of 2057358, see there for details.
Changes
Bugzilla/API/V1/BugUserLastVisit.pm:GET/POST /rest/bug_user_last_visitand/rest/bug_user_last_visit/<id>(login required), same JSON response shape as the legacy endpointsBugzilla/WebService/BugUserLastVisit.pmandBugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pmBugUserLastVisitentry fromWS_DISPATCHinBugzilla/WebService/Constants.pmand drop the correspondinguseline inBugzilla/WebService/Server/REST.pmBreaking change: removing the
WS_DISPATCHentry also removesBugUserLastVisit.get/updatefrom JSON-RPC and XML-RPC, not just the legacy REST dispatcher, since all three share that table. Native Mojo routes only serve REST. This matches the same tradeoff already made in the Classification and Bugzilla (system-info) migrations earlier in this series.Test plan
GET /rest/bug_user_last_visit(anonymous =>login_required, authenticated => list of last-visited bugs)GET /rest/bug_user_last_visit/<id>GET /rest/bug_user_last_visit?ids=<id>&ids=<id>POST /rest/bug_user_last_visit/<id>POST /rest/bug_user_last_visitwith{"ids":[...]}bodyOPTIONSon both routes returnsAllow: GET, POSTlast_visit_tswith trailingZ) matches the legacy endpointReferences