mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
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>
43 lines
1.6 KiB
Go
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))
|
|
}
|
|
}
|