package zddc import ( "os" "path/filepath" "testing" ) func TestIsAdmin(t *testing.T) { cases := []struct { name string zddcBody string // contents of /.zddc; empty string means no file email string want bool }{ { name: "no zddc file → not admin", zddcBody: "", email: "alice@example.com", want: false, }, { name: "zddc file with no admins key → not admin", zddcBody: "acl:\n allow: [\"*\"]\n", email: "alice@example.com", want: false, }, { name: "zddc file with empty admins list → not admin", zddcBody: "admins: []\n", email: "alice@example.com", want: false, }, { name: "exact-match admin → admin", zddcBody: "admins:\n - alice@example.com\n", email: "alice@example.com", want: true, }, { name: "domain glob admin → admin", zddcBody: "admins:\n - \"*@example.com\"\n", email: "alice@example.com", want: true, }, { name: "domain glob admin, wrong domain → not admin", zddcBody: "admins:\n - \"*@example.com\"\n", email: "alice@other.org", want: false, }, { name: "non-matching listed admin → not admin", zddcBody: "admins:\n - bob@example.com\n", email: "alice@example.com", want: false, }, { name: "empty email never matches even if pattern is *", zddcBody: "admins:\n - \"*\"\n", email: "", want: false, }, { name: "acl deny does not affect admins", zddcBody: "acl:\n deny: [\"*@example.com\"]\nadmins:\n - alice@example.com\n", email: "alice@example.com", want: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { root := t.TempDir() if tc.zddcBody != "" { if err := os.WriteFile(filepath.Join(root, ".zddc"), []byte(tc.zddcBody), 0o644); err != nil { t.Fatalf("write .zddc: %v", err) } } if got := IsAdmin(root, tc.email); got != tc.want { t.Errorf("IsAdmin(%q, %q) = %v, want %v", root, tc.email, got, tc.want) } }) } } // TestIsAdminSubdirIgnored documents that admins entries in subdirectory // .zddc files are NOT honored by IsAdmin — only the root .zddc grants the // server-wide super-admin role. Subtree admin authority for "fiefdom" // editing is a separate concept covered by IsSubtreeAdmin / CanEditZddc. func TestIsAdminSubdirIgnored(t *testing.T) { root := t.TempDir() sub := filepath.Join(root, "project") if err := os.MkdirAll(sub, 0o755); err != nil { t.Fatalf("mkdir: %v", err) } // Root has no admins; subdir tries to grant admin. if err := os.WriteFile(filepath.Join(root, ".zddc"), []byte("acl:\n allow: [\"*\"]\n"), 0o644); err != nil { t.Fatalf("write root .zddc: %v", err) } if err := os.WriteFile(filepath.Join(sub, ".zddc"), []byte("admins:\n - mallory@example.com\n"), 0o644); err != nil { t.Fatalf("write subdir .zddc: %v", err) } if IsAdmin(root, "mallory@example.com") { t.Error("subdir .zddc admins entry was honored — that is a privilege-escalation hole") } } // fixture writes a tree of .zddc files. Keys are paths relative to root; // the empty string means root itself ("/.zddc"). Values are file // contents. Intermediate directories are created as needed. Each path is // joined with ".zddc". func writeZddcTree(t *testing.T, root string, files map[string]string) { t.Helper() for rel, body := range files { dir := filepath.Join(root, rel) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatalf("mkdir %s: %v", dir, err) } // Always invalidate cache before/after writes inside a test so // subsequent calls re-read disk. Tests run with t.TempDir() so // there's no cross-test contamination, but the in-process cache // is global and may carry stale entries between subtests if a // prior subtest read the same path. InvalidateCache(dir) if body == "" { continue } if err := os.WriteFile(filepath.Join(dir, ".zddc"), []byte(body), 0o644); err != nil { t.Fatalf("write %s/.zddc: %v", rel, err) } } } func TestIsSubtreeAdmin(t *testing.T) { cases := []struct { name string files map[string]string dir string // relative to root email string want bool }{ { name: "no zddc anywhere → not admin", files: map[string]string{}, dir: "", email: "alice@example.com", want: false, }, { name: "root admin → admin of any subtree", files: map[string]string{ "": "admins:\n - alice@example.com\n", "projects/x": "", }, dir: "projects/x", email: "alice@example.com", want: true, }, { name: "subtree admin granted at intermediate level", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", "projects/x": "", }, dir: "projects/x", email: "alice@example.com", want: true, }, { name: "subtree admin granted at the leaf level itself", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", }, dir: "projects", email: "alice@example.com", want: true, }, { name: "non-admin in same subtree → not admin", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", }, dir: "projects", email: "bob@example.com", want: false, }, { name: "admin granted in sibling subtree does not leak", files: map[string]string{ "": "admins:\n - root@example.com\n", "foo": "admins:\n - alice@example.com\n", "bar": "", }, dir: "bar", email: "alice@example.com", want: false, }, { name: "glob admin", files: map[string]string{ "": "admins:\n - \"*@varasys.io\"\n", "projects": "", }, dir: "projects", email: "alice@varasys.io", want: true, }, { name: "empty email never admin", files: map[string]string{"": "admins:\n - \"*\"\n"}, dir: "", email: "", want: false, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { root := t.TempDir() writeZddcTree(t, root, tc.files) dir := filepath.Join(root, tc.dir) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatalf("mkdir target: %v", err) } InvalidateCache(dir) if got := IsSubtreeAdmin(root, dir, tc.email); got != tc.want { t.Errorf("IsSubtreeAdmin(%q, %q) = %v, want %v", tc.dir, tc.email, got, tc.want) } }) } } func TestCanEditZddc(t *testing.T) { cases := []struct { name string files map[string]string dir string email string want bool }{ { name: "root super-admin can edit root .zddc (bootstrap)", files: map[string]string{ "": "admins:\n - root@example.com\n", }, dir: "", email: "root@example.com", want: true, }, { name: "non-admin cannot edit root", files: map[string]string{ "": "admins:\n - root@example.com\n", }, dir: "", email: "alice@example.com", want: false, }, { name: "no zddc files at all → nobody edits root", files: map[string]string{}, dir: "", email: "anyone@example.com", want: false, }, { name: "root super-admin can edit any subtree file", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects/x": "", }, dir: "projects/x", email: "root@example.com", want: true, }, { name: "subtree admin can edit deeper file (strict ancestor satisfied)", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", "projects/x": "", }, dir: "projects/x", email: "alice@example.com", want: true, }, { name: "subtree admin CANNOT edit their own grant file (no strict ancestor for them)", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", }, dir: "projects", email: "alice@example.com", want: false, }, { name: "subtree admin CANNOT edit root", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", }, dir: "", email: "alice@example.com", want: false, }, { name: "subtree admin CANNOT edit sibling's grant file", files: map[string]string{ "": "admins:\n - root@example.com\n", "foo": "admins:\n - alice@example.com\n", "bar": "admins:\n - bob@example.com\n", }, dir: "bar", email: "alice@example.com", want: false, }, { name: "two-level delegation — mid-level admin edits leaf below their grant", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", "projects/sub": "admins:\n - bob@example.com\n", "projects/sub/x": "", }, dir: "projects/sub/x", email: "alice@example.com", want: true, }, { name: "two-level delegation — bob (mid-level admin) cannot edit own grant", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", "projects/sub": "admins:\n - bob@example.com\n", }, dir: "projects/sub", email: "bob@example.com", want: false, }, { name: "two-level delegation — bob can still edit deeper", files: map[string]string{ "": "admins:\n - root@example.com\n", "projects": "admins:\n - alice@example.com\n", "projects/sub": "admins:\n - bob@example.com\n", "projects/sub/x": "", }, dir: "projects/sub/x", email: "bob@example.com", want: true, }, { name: "mallory in a subdir admins list — original escalation case stays blocked", files: map[string]string{ "": "acl:\n allow: [\"*\"]\n", "project": "admins:\n - mallory@example.com\n", }, dir: "project", email: "mallory@example.com", want: false, }, { name: "glob root admin can edit anything", files: map[string]string{ "": "admins:\n - \"*@varasys.io\"\n", "projects/x": "", }, dir: "projects/x", email: "alice@varasys.io", want: true, }, { name: "empty email never edits", files: map[string]string{"": "admins:\n - \"*\"\n"}, dir: "", email: "", want: false, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { root := t.TempDir() writeZddcTree(t, root, tc.files) dir := filepath.Join(root, tc.dir) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatalf("mkdir target: %v", err) } InvalidateCache(dir) if got := CanEditZddc(root, dir, tc.email); got != tc.want { t.Errorf("CanEditZddc(dir=%q, email=%q) = %v, want %v", tc.dir, tc.email, got, tc.want) } }) } }