diff options
Diffstat (limited to 'internal/admin/api_entity_path_test.go')
| -rw-r--r-- | internal/admin/api_entity_path_test.go | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/internal/admin/api_entity_path_test.go b/internal/admin/api_entity_path_test.go new file mode 100644 index 0000000..7abf65f --- /dev/null +++ b/internal/admin/api_entity_path_test.go @@ -0,0 +1,62 @@ +package admin + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + "thehouseoficarus/internal/config" + "thehouseoficarus/internal/player" +) + +// TestEntityAPICannotEscapeData: entity create/rename IDs and directory +// targets join into file paths, so a traversal ID ("../../x") must be +// rejected instead of writing outside the data directory. +func TestEntityAPICannotEscapeData(t *testing.T) { + root := t.TempDir() + dir := filepath.Join(root, "data") + for _, sub := range []string{"objects", "players/accounts"} { + if err := os.MkdirAll(filepath.Join(dir, sub), 0o700); err != nil { + t.Fatal(err) + } + } + accounts := player.NewAccountStore(dir) + if err := accounts.SaveAccount(&player.Account{Name: "auditor", Admin: true}); err != nil { + t.Fatal(err) + } + s, err := NewServer(config.Default(), false, accounts, nil, nil, nil, nil, dir, + nil, nil, nil, nil, filepath.Join(dir, "config.yaml"), + func(fn func()) bool { fn(); return true }) + if err != nil { + t.Fatal(err) + } + cookieRec := httptest.NewRecorder() + s.setAuthCookie(cookieRec, "auditor") + cookie := cookieRec.Result().Cookies()[0] + + post := func(path, body string) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body)) + req.AddCookie(cookie) + rec := httptest.NewRecorder() + s.ServeHTTP(rec, req) + return rec + } + + // Traversal entity ID via the structured create route. + if rec := post("/api/objects", `{"id":"../../outside","name":"Escaped object"}`); rec.Code != http.StatusBadRequest { + t.Fatalf("traversal object id: status %d body %s", rec.Code, rec.Body.String()) + } + // Traversal new ID via the entity rename route: the rename handler + // must reject the request, not rename anything. + rec := post("/api/objects/rename", `{"id":"test","new_id":"../../outside"}`) + if !strings.Contains(rec.Body.String(), "error") { + t.Fatalf("traversal rename id not rejected: status %d body %s", rec.Code, rec.Body.String()) + } + // Nothing may exist outside the data directory. + if _, err := os.Stat(filepath.Join(root, "outside.yaml")); !os.IsNotExist(err) { + t.Fatal("entity API escaped the data directory") + } +} |
