Conversation
Error messages from native-Mojo REST controllers were being word-wrapped at 72 columns, introducing literal newlines that broke message-content assertions like rest_group_create.t.
…failure The 'auth_failure' error template only has a branch for 'groups' (plural), 'group' fell through to the empty default, so the unauthorized-user message read "...not authorized to add new ." with the object word missing.
| # object, so this filter is a no-op that leaves $groups untouched in | ||
| # practice rather than actually filtering by blessability. | ||
| if (!$can_see_groups) { | ||
| $groups = [map { $user->can_bless($_) } @{$groups}]; |
There was a problem hiding this comment.
this filter isn't a no-op — the comment above is wrong about what happens
can_bless returns 0/1 (Bugzilla/User.pm:2116), so map replaces each Bugzilla::Group object with a plain scalar. the next line then calls ->id on 0 and dies
reachable: a user not in can_see_groups but with bless privileges passes the guard at line 147, gets $groups = $user->bless_groups, and GET /rest/group returns a 500 instead of their blessable groups. admins never hit it, which is why the qa tests are green
the line is a faithful copy of the legacy code so it's pre-existing, but since the PR documents it as harmless it's worth fixing here instead:
$groups = [grep { $user->can_bless($_->id) } @$groups];that matches how _get_group_membership already calls it on line 239
There was a problem hiding this comment.
You right, my comment was wrong: this isn't a harmless no-op, it crashes.
Fixed with $user->can_bless($_->id), matching _get_group_membership's usage.
=> Fixed in "Bug 2065173 - Fix crash filtering groups by blessability"
| $group->check_can_be_edited(); | ||
| } | ||
|
|
||
| my %values = %$params; |
There was a problem hiding this comment.
cookie-authenticated PUT breaks here
_request_params returns $self->req->params->to_hash unfiltered, and Bugzilla/App/Plugin/Login.pm:68 reads Bugzilla_api_token without deleting it. so it survives into %values → set_all → set_Bugzilla_api_token → ThrowCodeError('unknown_method')
legacy worked because Bugzilla::Auth::Login::Cookie did delete Bugzilla->input_params->{Bugzilla_api_token} before the method ran. that path isn't used by Mojo controllers, so this is a new regression
include_fields/exclude_fields hit the same wall. suggest whitelisting the documented fields (name, description, user_regexp, is_active, icon_url) before set_all
There was a problem hiding this comment.
Good catch. As suggested, switched to whitelisting the five documented update fields (name, description, user_regexp, is_active, icon_url) instead of blacklisting names/ids, so stray keys like Bugzilla_api_token or include_fields/exclude_fields no longer reach set_all().
=> Fixed in "Bug 2065173 - Whitelist update() fields instead of blacklisting names/ids"
| my $routes = $r->under( | ||
| '/group' => sub { Bugzilla->usage_mode(USAGE_MODE_MOJO_REST); }); | ||
| $routes->get('/')->to('V1::Group#get'); | ||
| $routes->get('/:id')->to('V1::Group#get'); |
There was a problem hiding this comment.
:id won't match a group name containing a dot — the : placeholder stops at ., while the legacy resource regex was qr{^/group/([^/]+)$}
GET /rest/group/my.group and PUT /rest/group/my.group would return an HTML 404 instead of JSON. Bugzilla::Group::_check_name only checks for emptiness and uniqueness, so dotted names are allowed
use the relaxed placeholder '/#id' on lines 30, 32 and 34, same as Bugzilla/API/V1/Classification.pm:21. a qa case with a dotted name would lock it in
There was a problem hiding this comment.
As suggested, switched to relaxed #id placeholder (same as Classification.pm).
=> Fixed in "Bug 2065173 - Use relaxed #id placeholder to allow dots in group names"
| foreach my $field (keys %{$changes{$group->id}}) { | ||
| my $change = $changes{$group->id}->{$field}; | ||
| $hash{changes}{$field} | ||
| = {removed => "$change->[0]", added => "$change->[1]"}; |
There was a problem hiding this comment.
interpolating $change->[0] turns a legit undef into "" and logs an uninitialized-value warning
legacy used $self->type('string', ...), which passed undef through as JSON null. Bugzilla::Object::update supports transitions from or to undef (e.g. icon_url going from NULL), so this changes the response shape
removed => defined $change->[0] ? "$change->[0]" : undef,
added => defined $change->[1] ? "$change->[1]" : undef,There was a problem hiding this comment.
Fixed as suggested, Thanks!
=> Fixed in "Bug 2065173 - Preserve null in changes when a field goes to/from undef"
_request_params duplicated the same query-string/JSON-body merge logic already written for BugUserLastVisit.pm (bug 2065171). Now call a single shared merge_request_params helper, so it's a one-place change to drop later if query-string-on-POST support is ever removed. Please note that BugUserLastVisit.pm (bug 2065171) is being updated separately to call the same helper instead of its own copy.
|
Pushed a follow-up commit extracting |
can_bless() takes a group id, not a Group object. Passing the object made every entry falsy, and the next line's ->id call on that died. Reachable by bless-privileged users without can_see_groups. Was ported faithfully from legacy as a described no-op, although it's actually a genuine crash, so fixing it here.
…/ids set_all() throws unknown_method for any stray key without a matching set_<key> method. Cookie-authenticated PUT hit this via Bugzilla_api_token (legacy deleted it before the method ran, the Mojo cookie-auth path doesn't), include_fields/exclude_fields hit it too. Whitelist the update fields instead.
| return $usage_mode == USAGE_MODE_JSON || $usage_mode == USAGE_MODE_REST; | ||
| return $usage_mode == USAGE_MODE_JSON | ||
| || $usage_mode == USAGE_MODE_REST | ||
| || $usage_mode == USAGE_MODE_MOJO_REST; |
There was a problem hiding this comment.
this isn't scoped to Group - it changes i_am_webservice() for every request running in USAGE_MODE_MOJO_REST, which is all ~15 Bugzilla::API::V1::* controllers plus the PhabBugz/Webhooks/SearchAPI/GitHubPullRequests extension controllers
concrete downstream effects I can see:
Bugzilla/Template.pm:866stopswrap_comment-ing error messages, so themessagefield of every Mojo REST error response changes shape (this is presumably the motivation, but it's a response-format change for already-shipped endpoints)extensions/RestrictComments/Extension.pm:58flips: bug updates throughAPI/V1/Github.pmand PhabBugz previously clearedrestrict_commentson every touched bug when the actor was inrestrict_comments_enable_group, and now won'tBugzilla.pm:593log_user_requeststarts logging Mojo REST requests whenlog_user_requestsis onBugzilla/Auth/Login/APIKey.pm:46andBugzilla/Auth/Verify/DB.pm:127change gating for any code path that reachesBugzilla->loginunder MOJO_REST
none of that is wrong as far as I can tell, but it doesn't belong silently inside a Group migration. please either split it into its own bug or at minimum call it out in the description and add a test pinning the new error-message shape
|
|
||
| if (length $c->req->body) { | ||
| my $body_params; | ||
| try { $body_params = decode_json($c->req->body); } |
There was a problem hiding this comment.
swallowing the decode error is a regression from the legacy layer. _retrieve_json_params in Bugzilla/WebService/Server/REST.pm:343 did ThrowUserError('json_rpc_invalid_params', {err_msg => $@})
so POST /rest/group with a truncated body now reports You must enter a name instead of telling the client its JSON is broken
suggest rethrowing as json_rpc_invalid_params when the body is non-empty and looks like JSON. separately, the length $c->req->body guard has no method check, so a GET with a body gets its JSON merged in too - legacy only did this for non-GET
|
|
||
| # Whitelist the documented update fields; set_all() throws unknown_method | ||
| # for any stray key (e.g. Bugzilla_api_token, include_fields). | ||
| my %values = map { $_ => $params->{$_} } |
There was a problem hiding this comment.
this whitelist turns a hard error into a silent no-op. legacy passed everything except ids/names to set_all, so Bugzilla::Object::set_all raised unknown_method on anything unrecognized
now PUT /rest/group/5 with {"is_bug_group": 0} or a typo like {"userregexp": "@foo$"} returns 200 with changes: {} and the client has no way to tell its field was ignored
prefer deleting the known meta keys (ids, names, Bugzilla_api_token, Bugzilla_login, Bugzilla_password, include_fields, exclude_fields) and passing the rest through, so unknown fields still error
| sub options { | ||
| my ($self) = @_; | ||
|
|
||
| $self->res->headers->header('Allow' => 'GET, POST, PUT'); |
There was a problem hiding this comment.
one Allow value is shared by both routes, so OPTIONS /rest/group advertises PUT (no route, 404) and OPTIONS /rest/group/5 advertises POST (no route, 404)
legacy computed this per path - GET, POST on /group and GET, PUT on /group/<id_or_name> (see the deleted Resources/Group.pm and the note at Bugzilla/WebService/Server/REST.pm:615). since Access-Control-Allow-Methods is also set from it, a browser preflight gets told POST /rest/group/5 is fine
suggest passing the allowed methods through the route, e.g. ->to('V1::Group#options', allow => 'GET, POST')
| } | ||
| } | ||
|
|
||
| # Filter groups by blessability if user is not allowed to see all groups. |
There was a problem hiding this comment.
the comment and the PR description disagree with the code, and both descriptions of the legacy behavior are wrong
the description says this bug is "preserved, not fixed", but line 191 does fix it. and legacy wasn't a no-op filter - it was $groups = [map { $user->can_bless($_) } @{$groups}], a map, not a grep. can_bless($group_object) numifies the object ref to 0 and returns 0, so legacy replaced every element with 0 and then _group_to_hash called ->id on 0, i.e. GET /rest/group/<id> as a blesser without can_see_groups returned a 500
the fix is right, but it's a real behavior change (those users now get a filtered list). please fix the description and comment, and add a case to qa/t/rest_group_get.t for a user who can bless one group and requests a different one
| # Show only users in visible groups. | ||
| $visible_groups = $user->visible_groups_inherited; | ||
|
|
||
| if (scalar @$visible_groups) { |
There was a problem hiding this comment.
when usevisibilitygroups is on, the caller isn't in editusers, and visible_groups_inherited comes back empty, $query is never extended - but $visible_groups is [], which is truthy, so the ThrowUserError on line 256 doesn't fire and line 258 appends ' AND ' . sql_in(...) to a bare SELECT userid FROM profiles
that's SELECT userid FROM profiles AND ugm.group_id IN (...) - a SQL error, so GET /rest/group/<id>?membership=1 500s
carried over verbatim from the legacy code, but you're rewriting this function anyway. unless $visible_groups && (!ref $visible_groups || @$visible_groups) or just setting $visible_groups = undef when the list is empty would close it
Summary
Ports
Bugzilla::WebService::Group'screate/update/getmethods into a nativeBugzilla::API::V1::GroupMojo controller, mirroring the pattern already used for Classification/Component/Teams/Reminders/Configuration/Bugzilla (system info)/BugUserLastVisit.This is a child bug of 2057358, see there for details.
Changes
Bugzilla/API/V1/Group.pm:GET/POST /rest/groupandGET/PUT /rest/group/<id_or_name>(login +creategroupsrequired for create/update), same JSON response shape as the legacy endpointsBugzilla/WebService/Group.pmandBugzilla/WebService/Server/REST/Resources/Group.pmGroupentry fromWS_DISPATCHinBugzilla/WebService/Constants.pm, the correspondinguseline inBugzilla/WebService/Server/REST.pm, and the POD entry inBugzilla/WebService.pmBreaking change: removing the
WS_DISPATCHentry also removesGroup.create/update/getfrom 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, Bugzilla (system-info), and BugUserLastVisit migrations earlier in this series.Known pre-existing bug, preserved, not fixed:
get()'s "filter by blessability" step for non-can_see_groupsusers calls$user->can_bless($group)with aGroupobject wherecan_blessexpects a group id, so the filter is a no-op in both the legacy and new code. Flagging for review rather than silently fixing it in a migration PR.Test plan
GET /rest/group/GET /rest/group?ids=1&ids=2/GET /rest/group?names=adminGET /rest/group/<id>/GET /rest/group/<name>GET /rest/group/<id>?membership=1POST /rest/group(name/description required, duplicate name, invaliduser_regexp)PUT /rest/group/<id_or_name>(protectedadmin/insider group rejected for non-admins, perqa/t/rest_group_update_protected.t)OPTIONSon both routes returnsAllow: GET, POST, PUTqa/t/rest_group_get.t,qa/t/rest_group_create.t,qa/t/rest_group_update_protected.tshould pass unchangedReferences