Files
OwnCord/Server/service/channel_test.go
T
J3vbandClaude Fable 5 a4eca1a55a fix(perms): own the server-scoped rule in HasServerPerm and fail closed on override-fetch errors (D13)
Closes audit finding A-2026-07-16, two defects in the same rule:

- permissions.HasServerPerm (admin bypass OR all-of bit test) replaces
  the hand-rolled copies in api.RequirePermission (whose raw test was
  any-of for multi-bit masks) and ModerationService.requireBanPermission.
  RequirePermission's doc comment now states the scope contract: role
  bitfield only, channel overrides deliberately not consulted.
- PermissionService.getOrPopulate and ChannelService.ListVisibleChannels
  no longer substitute an empty override map when
  GetAllChannelPermissionsForRole errors. That silently dropped every
  channel-level deny — and the permission cache then served the degraded
  snapshot for permCacheTTL (30s) across ~25 callers. Both fail closed
  now; admins skip the fetch entirely (they bypass channel checks).
- PermissionService.HasChannelPerm delegates to Checker.HasChannelPermBatch
  and MessageService.GetAccessibleChannelIDs to VisibleChannelIDs — the
  missed fifth D9 site, making that closure true rather than aspirational.
- AuthMiddleware rejects a dangling role_id (GetRoleByID returns nil,
  nil) with 401 instead of putting a nil role in the request context.

Locked by failing-first tests: override-fetch-error denies (cached and
uncached paths), admin-outage skip, multi-bit all-of, channel allow
override must not grant a server-wide route, 403 locks on both
RequirePermission routes, dangling-role 401.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:45:34 +02:00

43 lines
1.6 KiB
Go

package service
import (
"context"
"errors"
"testing"
"github.com/owncord/server/db"
"github.com/owncord/server/permissions"
)
// TestListVisibleChannels_OverrideFetchErrorFailsClosed is the uncached half of
// the same fail-open bug as TestHasChannelPerm_OverrideFetchErrorDenies: an
// empty override map here would list every channel the role is explicitly
// denied. The listing must error instead of leaking the denied channel.
func TestListVisibleChannels_OverrideFetchErrorFailsClosed(t *testing.T) {
database := newTestDB(t)
seedRole(t, database, &db.Role{
ID: permissions.MemberRoleID,
Name: "member",
Permissions: permissions.SendMessages | permissions.ReadMessages | permissions.AddReactions,
Position: 1,
})
seedUserRole(t, database, 1, permissions.MemberRoleID)
seedChannel(t, database, &db.Channel{ID: 10, Name: "secret", Type: "text"})
seedChannelOverride(t, database, permissions.MemberRoleID, 10, 0, permissions.ReadMessages)
st := errOverrideStore{DB: database}
permSvc := NewPermissionService(st, permissions.NewChecker(database))
svc := NewChannelService(st, permSvc)
// Either failing path is acceptable and both are ErrInternal: the permission
// cache may short-circuit on its own fail-closed nil, or ListVisibleChannels'
// own override branch may error. What must never happen is a 200 listing.
got, err := svc.ListVisibleChannels(context.Background(), 1)
if !errors.Is(err, ErrInternal) {
t.Fatalf("ListVisibleChannels err = %v, want ErrInternal", err)
}
if got != nil {
t.Fatalf("ListVisibleChannels returned %d channels on override fetch failure, want none", len(got))
}
}