Repository navigation
fix(auth): apply the view-only group-combination rule when a group's permissions change - #1462
Conversation
…permissions change LeanerCloud#1205 refuses a view-only plus write-capable group combination on user create and update, but UpdateGroup could still produce one with no user write: adding a write permission to a group whose members also belong to a view-only group (or making a group view-only while its members hold a write-capable one) silently contradicted every such member. UpdateGroupAPI now builds the updated group, and when its role in the rule changes (view-only, write-capable or neutral) lists the users and checks each member's combination before and after. If any member who was consistent would become contradictory, the update is refused with ErrContradictoryGroups naming the group and how many members conflict; PUT /api/groups/{id} maps it to 400. Members who were already contradictory do not block it, matching the unchanged-membership rule on user edits. An update that keeps the role, including any update that omits or repeats the permissions, takes the fast path and reads no user. The check runs after the grant and account ceilings, so a 403 still wins over the 400. Like the LeanerCloud#1205 check it is an accident guard, not a security boundary. checkGroupCombination now shares its classification with the new check through a group loader, which the new check caches per call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins the LeanerCloud#1409 rule through UpdateGroupAPI: adding a write permission to a group whose members also hold a view-only group is refused with the member count, as is making a group view-only while members hold a write-capable one, and a first permission on an empty group is checked too. A role change with no conflicting member saves, a member who was already contradictory does not block, and an update that keeps the group's role reads no user. Store errors fail closed and are not reported as a contradiction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UpdateGroupAPI now refuses a permission change that would give a member
both a view-only and a write-capable group (ErrContradictoryGroups).
mapGroupAuthError did not know that sentinel, so the refusal surfaced as
a 500. Map it to 400 like the other malformed-input group errors, and
document the case on PUT /api/groups/{id}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Opened at b0f8126: group updates that change a group's view-only/write-capable role are refused (400) when they would contradict a member's group combination. Local checks are listed in the PR body. |
|
Label mirror: issue #1409 has no triage labels (priority/severity/urgency/impact/effort/type or triaged), so none were copied to this PR. The issue needs triage. |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rE2. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Reviewer: rE3. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 2 approving independent review(s) on |
Closes #1409
Root cause
#1205 refuses a view-only plus write-capable group combination when a user is created or updated (
checkGroupCombination,internal/auth/group_combination.go).UpdateGroupAPIdid not apply the rule, so a group update could create the contradiction for every member without any user write. Two ways this happened: adding a write permission to a group whose members also belong to a view-only group, or making a group view-only while its members belong to a write-capable group.Fix
UpdateGroupAPIbuilds the updated group as a copy and calls the newcheckGroupPermissionChange(prior, updated)before it saves. This runs after the grant and account ceiling checks, so a 403 still wins over the 400, matching the user-write order.ErrContradictoryGroupserror names the group and the number of affected members, e.g.group "Team" would grant write permissions, but 2 of its members also belong to a view-only group; remove them from one of the two groups first.checkGroupCombinationand the new check share the same classification through a group loader. The new check caches loaded groups for the length of one call.mapGroupAuthErrormapsErrContradictoryGroupsto 400 onPUT /api/groups/{id}, which otherwise would have been a 500.openapi.yamldocuments the case.I went with the issue's "refuse with a 400 listing how many members would be affected" option rather than "flag them". Like #1205, this is an accident guard, not a security boundary. There is a window where a concurrent user write between the member check and the group save is not seen.
Verification
internal/auth/group_permission_change_test.go, run throughUpdateGroupAPI, covers:ListUserscall;ListUsersand group-lookup store errors: fail closed, not reported as a contradiction.UpdateGroupAPI(the un-stubbedUpdateGroupwrite is reached) and pass with the fix.TestUpdateGroup_ContradictoryMembersMaps400covers the handler mapping.ListUserswith no members.go build,go vet(default,integration,e2e), golangci-lint v2.10.1 oninternal/authandinternal/apiwith and without--build-tags integration: 0 issues. gocyclo -over 10 on the changed files: clean.go test -race -shortoninternal/auth,internal/api,internal/mocks(including the vacuous-assertion guard) andinternal/server: ok.go test -race -tags integration ./internal/auth/: ok. Two earlier runs timed out starting Postgres on the overloaded shared Docker host.scripts/test-git-secrets-allowlist.shpass.🤖 Generated with Claude Code