owpengram-server/cmd/telesrv-admin/routescope_test.go
2026-09-08 01:53:49 +03:00

134 lines
4.7 KiB
Go

package main
import (
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
// The panel is deny-by-default: an API route must say which right it belongs
// to. Registering one with a bare requireAuthAPI would make it answer to every
// signed-in operator regardless of what they were granted -- which is how a
// scoped account quietly gets the run of the place.
//
// This reads the source rather than the routing table because that is where the
// mistake is made: it fails on the line someone is about to add, and names it.
func TestEveryAPIRouteDeclaresAScope(t *testing.T) {
// Every /api route must be registered through a wrapper that names a
// permission. Whitelisting the wrappers rather than blacklisting the bare
// one is what makes this hold for helpers added later: a new wrapper is
// unknown here until someone adds it deliberately, so it fails closed.
allowed := []string{
"s.scopedRoute(",
"s.scopedRouteAll(",
"s.requirePermission(",
"s.requireAdminsManage(",
"s.serverManage(",
"s.verificationRead(",
"s.botVerificationRead(",
"s.botVerificationManage(",
}
// Routes that are reachable before a session exists, each for a stated
// reason: /api/login is the way in (it carries its own credential), and the
// two branding routes feed the login screen with the server name and icon
// that owpengram-server already publishes to every client.
exempt := map[string]bool{
"POST /api/login": true,
"GET /api/public/branding": true,
"GET /api/public/icon": true,
}
route := regexp.MustCompile(`mux\.Handle(Func)?\("([A-Z]+ /api/[^"]*)"`)
entries, err := os.ReadDir(".")
if err != nil {
t.Fatalf("read package directory: %v", err)
}
var offenders []string
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
source, err := os.ReadFile(filepath.Clean(name))
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
for _, line := range strings.Split(string(source), "\n") {
m := route.FindStringSubmatch(line)
if m == nil || exempt[m[2]] {
continue
}
guarded := false
for _, wrapper := range allowed {
if strings.Contains(line, wrapper) {
guarded = true
break
}
}
if !guarded {
offenders = append(offenders, name+": "+strings.TrimSpace(line))
}
}
}
if len(offenders) > 0 {
t.Fatalf("these routes are registered without a permission -- wrap them in s.scopedRoute(permission, ...):\n %s",
strings.Join(offenders, "\n "))
}
}
// Every right the account editor offers must be one the routes actually check,
// and vice versa -- a name in one list and not the other is either a right
// nobody can be granted or a checkbox that grants nothing.
func TestAssignablePermissionsMatchWhatRoutesEnforce(t *testing.T) {
assignable := make(map[string]bool, len(assignablePermissions()))
for _, p := range assignablePermissions() {
if p == permissionSessionOnly {
t.Fatal("permissionSessionOnly is not a grantable right and must not be offered")
}
if assignable[p] {
t.Fatalf("permission %q is offered twice", p)
}
assignable[p] = true
}
if len(assignable) == 0 {
t.Fatal("no assignable permissions")
}
// Spot-check the pairs the sections are built around, so a rename that
// misses one half is caught here rather than by an operator who suddenly
// cannot open a page.
for _, required := range []string{
permissionAccountsRead, permissionAccountsManage,
permissionChannelsRead, permissionChannelsManage,
permissionBotsRead, permissionBotsManage,
permissionMessagesRead, permissionMessagesManage,
permissionContentRead, permissionContentManage,
permissionUsernamesRead, permissionUsernamesManage,
permissionStorageRead, permissionStorageManage,
permissionBroadcastsRead, permissionBroadcastsSend,
permissionModerationReview, permissionDashboardRead,
permissionAdminsManage, permissionServerManage,
} {
if !assignable[required] {
t.Errorf("permission %q is enforced somewhere but cannot be granted", required)
}
}
}
// permissionSessionOnly must stay the empty string: scopedRoute distinguishes
// "a session is enough" from a real right by that emptiness, and panelPermissions
// drops empty entries, so it can never be smuggled into an account's list.
func TestSessionOnlyIsNotGrantable(t *testing.T) {
if permissionSessionOnly != "" {
t.Fatalf("permissionSessionOnly = %q, want the empty string", permissionSessionOnly)
}
perms := newPanelPermissions([]string{permissionSessionOnly, permissionAccountsRead})
if perms.Has(permissionSessionOnly) {
t.Fatal("an empty permission was treated as granted")
}
if !perms.Has(permissionAccountsRead) {
t.Fatal("a real permission alongside it was lost")
}
}