71 lines
2.4 KiB
Go
71 lines
2.4 KiB
Go
package api
|
|
|
|
import (
|
|
"git.misaka.ren/m1saka/dsh-web/internal/store"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestWorkspaceInstanceScope(t *testing.T) {
|
|
a, sid := runtimeTestAPI(t, func(w http.ResponseWriter, r *http.Request) { t.Error("unexpected Docker call") })
|
|
for _, x := range []struct{ id, uid string }{{"one", "owner"}, {"two", "owner"}, {"foreign", "someone"}} {
|
|
if err := a.s.AddInstance(store.Instance{ID: x.id, UID: x.uid}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := filepath.Join(a.root, "workspaces", x.uid, "instances", x.id, "home")
|
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, x.id+".txt"), []byte(x.id), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, tc := range []struct {
|
|
query string
|
|
status int
|
|
contains string
|
|
}{
|
|
{"instance=one", 200, "one.txt"}, {"instance=two", 200, "two.txt"},
|
|
{"instance=foreign", 404, ""}, {"instance=missing", 404, ""},
|
|
{"instance=one&path=../two", 400, ""},
|
|
} {
|
|
w := httptest.NewRecorder()
|
|
a.workspace(w, runtimeRequest("GET", "/api/ws/tree?"+tc.query, "", sid))
|
|
if w.Code != tc.status || tc.contains != "" && !strings.Contains(w.Body.String(), tc.contains) {
|
|
t.Fatalf("%s: %d %s", tc.query, w.Code, w.Body.String())
|
|
}
|
|
}
|
|
w := httptest.NewRecorder()
|
|
a.workspace(w, runtimeRequest("POST", "/api/ws/mkdir?instance=two", `{"path":"created"}`, sid))
|
|
if w.Code != 200 {
|
|
t.Fatal(w.Code, w.Body.String())
|
|
}
|
|
if _, err := os.Stat(filepath.Join(a.root, "workspaces/owner/instances/two/home/created")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(a.root, "workspaces/owner/instances/one/home/created")); !os.IsNotExist(err) {
|
|
t.Fatal("cross instance write")
|
|
}
|
|
dir := filepath.Join(a.root, "workspaces/owner/instances/one/home")
|
|
if err := os.Symlink(t.TempDir(), filepath.Join(dir, "escape")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w = httptest.NewRecorder()
|
|
a.workspace(w, runtimeRequest("GET", "/api/ws/tree?instance=one&path=escape", "", sid))
|
|
if w.Code != 404 {
|
|
t.Fatal("symlink traversal allowed", w.Code)
|
|
}
|
|
if _, err := a.s.DB.Exec("UPDATE instances SET status='error' WHERE id='two'"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w = httptest.NewRecorder()
|
|
a.workspace(w, runtimeRequest("POST", "/api/ws/mkdir?instance=two", "{\"path\":\"blocked\"}", sid))
|
|
if w.Code != 409 {
|
|
t.Fatal("recovery write permitted", w.Code)
|
|
}
|
|
}
|