package handler import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "codeberg.org/VARASYS/ZDDC/zddc/internal/config" "codeberg.org/VARASYS/ZDDC/zddc/internal/zddc" ) // TestServeDirectoryRootIsPublic asserts that the landing page (the root // directory listing) is reachable by anyone, including anonymous callers // whose email is empty AND whose access would be denied by a restrictive // root .zddc. Per-project filtering inside fs.ListDirectory still hides // directories the caller can't reach (separately verified below). // // The behavior was changed when "Everyone needs to have access to the // landing page" became the explicit requirement; this test is the // regression guard. func TestServeDirectoryRootIsPublic(t *testing.T) { root := t.TempDir() // Restrictive root .zddc — only admin@example.com is allowed by ACL, // nothing else. A user without that email would have been 403'd before // the bypass. if err := os.WriteFile(filepath.Join(root, ".zddc"), []byte("admins:\n - admin@example.com\nacl:\n permissions:\n admin@example.com: rwcd\n"), 0o644); err != nil { t.Fatalf("write root .zddc: %v", err) } // One project visible to everyone, one only to admin. for _, name := range []string{"PublicProj", "PrivateProj"} { if err := os.MkdirAll(filepath.Join(root, name), 0o755); err != nil { t.Fatalf("mkdir %s: %v", name, err) } } if err := os.WriteFile(filepath.Join(root, "PublicProj", ".zddc"), []byte("acl:\n permissions:\n \"*\": rwcd\n"), 0o644); err != nil { t.Fatalf("write PublicProj .zddc: %v", err) } if err := os.WriteFile(filepath.Join(root, "PrivateProj", ".zddc"), []byte("acl:\n permissions:\n admin@example.com: rwcd\n"), 0o644); err != nil { t.Fatalf("write PrivateProj .zddc: %v", err) } cfg := config.Config{Root: root, EmailHeader: "X-Auth-Request-Email"} t.Run("anonymous JSON GET / does not 403", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Accept", "application/json") // Anonymous: empty email in context. req = req.WithContext(context.WithValue(req.Context(), EmailKey, "")) rec := httptest.NewRecorder() ServeDirectory(cfg, nil, rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200 (root is public); body = %s", rec.Code, rec.Body.String()) } }) t.Run("anonymous JSON GET / hides private projects", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Accept", "application/json") req = req.WithContext(context.WithValue(req.Context(), EmailKey, "")) rec := httptest.NewRecorder() ServeDirectory(cfg, nil, rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body = %s", rec.Code, rec.Body.String()) } var entries []map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &entries); err != nil { t.Fatalf("invalid JSON: %v\n%s", err, rec.Body.String()) } names := map[string]bool{} for _, e := range entries { if n, ok := e["name"].(string); ok { names[n] = true } } if !names["PublicProj/"] { t.Errorf("PublicProj missing from anonymous listing: %v", names) } if names["PrivateProj/"] { t.Errorf("PrivateProj leaked to anonymous listing: %v", names) } }) t.Run("admin JSON GET / sees both projects", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Accept", "application/json") req = req.WithContext(context.WithValue(req.Context(), EmailKey, "admin@example.com")) rec := httptest.NewRecorder() ServeDirectory(cfg, nil, rec, req) if rec.Code != http.StatusOK { t.Fatalf("admin status = %d, want 200", rec.Code) } var entries []map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &entries); err != nil { t.Fatalf("invalid JSON: %v", err) } if len(entries) != 2 { t.Errorf("admin should see both projects; got %d", len(entries)) } }) t.Run("anonymous still gets 403 on private subdirectory", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/PrivateProj/", nil) req.Header.Set("Accept", "application/json") req = req.WithContext(context.WithValue(req.Context(), EmailKey, "")) rec := httptest.NewRecorder() ServeDirectory(cfg, nil, rec, req) if rec.Code != http.StatusForbidden { t.Errorf("private subdir for anonymous: status = %d, want 403", rec.Code) } }) } // TestServeDirectoryRedirectsTableRowsDir asserts that an HTML GET on a // directory containing a table.yaml bounces to that dir's table.html URL // (in-dir convention: /