package server import ( "bytes" "encoding/json" "errors" "io" "log" "os" "path/filepath" "strings" "testing" "time" "cc.tiredsysadmin.keeporg/internal/sqldb" "cc.tiredsysadmin.keeporg/internal/sqldb/sqldbtest" "cc.tiredsysadmin.keeporg/internal/store" ) // A data directory written by a server before 0.11 is imported on the first // start: accounts, sessions and every stored version, after which the old // files are set aside. func TestOldDataDirectoryIsImported(t *testing.T) { dir := t.TempDir() old := map[string]any{ "users": map[string]any{"u1": map[string]any{"id": "u1", "username": "alice"}}, "groups": map[string]any{"g1": map[string]any{"id": "g1", "name": "everyone"}}, "databases": map[string]any{"d1": map[string]any{"id": "d1", "name": "Team", "owner_group_id": "g1", "merge_strategy": "server-wins", "version": 2}}, "sessions": map[string]any{"old-token": map[string]any{"token": "old-token", "user_id": "u1", "expires_at": time.Now().Add(time.Hour)}}, "memberships": []any{map[string]any{"user_id": "u1", "group_id": "g1", "role": "admin"}}, } b, _ := json.Marshal(old) os.WriteFile(filepath.Join(dir, "state.json"), b, 0o600) os.MkdirAll(filepath.Join(dir, "blobs", "d1"), 0o700) for v, body := range map[string]string{"1": "first", "2": "second"} { os.WriteFile(filepath.Join(dir, "blobs", "d1", "0000000000000000000"+v+".kdbx"), []byte(body), 0o600) } var logs bytes.Buffer cfg := Config{DataDir: dir, Database: sqldbtest.URL(t), MasterKeyFile: filepath.Join(t.TempDir(), "mk")} srv, err := New(cfg, log.New(&logs, "", 0)) if err != nil { t.Fatal(err) } defer srv.Close() srv.Store().Read(func(st *store.State) { if st.Users["u1"] == nil || st.Sessions["old-token"] == nil || len(st.Memberships) != 1 { t.Fatalf("imported state = %+v", st) } }) if got, err := srv.blobs.Get("d1", 2); err != nil || string(got) != "second" { t.Fatalf("imported version 2 = %q, %v", got, err) } if _, err := os.Stat(filepath.Join(dir, "state.json")); !errors.Is(err, os.ErrNotExist) { t.Fatal("state.json was left in place, to be imported again") } if !strings.Contains(logs.String(), "delete them") { t.Fatalf("the log does not ask for the old files to be deleted:\n%s", logs.String()) } // A second start finds the database populated and imports nothing. srv.Close() again, err := New(cfg, log.New(io.Discard, "", 0)) if err != nil { t.Fatal(err) } defer again.Close() again.Store().Read(func(st *store.State) { if len(st.Users) != 1 { t.Fatalf("users after restart = %d", len(st.Users)) } }) } func TestServerRefusesAnotherMasterKey(t *testing.T) { url := sqldbtest.URL(t) cfg := Config{DataDir: t.TempDir(), Database: url, MasterKeyFile: filepath.Join(t.TempDir(), "mk")} srv, err := New(cfg, log.New(io.Discard, "", 0)) if err != nil { t.Fatal(err) } srv.Close() cfg.MasterKeyFile = filepath.Join(t.TempDir(), "another") if _, err := New(cfg, log.New(io.Discard, "", 0)); !errors.Is(err, sqldb.ErrWrongKey) { t.Fatalf("started with a different master key: %v", err) } } func TestServerRefusesToStartWithoutItsMasterKey(t *testing.T) { t.Setenv("KEEPORG_MASTER_KEY_FILE", filepath.Join(t.TempDir(), "not-mounted")) cfg := Config{DataDir: t.TempDir(), Database: sqldbtest.URL(t)} if _, err := New(cfg, log.New(io.Discard, "", 0)); err == nil || !strings.Contains(err.Error(), "master key") { t.Fatalf("started without its master key: %v", err) } }