package server import ( "net/http" "net/http/httptest" "net/url" "os" "strings" "testing" "warpbox/lib/boxstore" "warpbox/lib/metastore" ) func TestAccountBoxManagerAdminCanViewAndEdit(t *testing.T) { app, admin := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, admin) id := "abababababababababababababababab" createIndexedBox(t, app, id, "", "", 10, false) response := getAccountBoxManager(router, session, id) if response.Code != http.StatusOK { t.Fatalf("expected manager page, got %d body=%s", response.Code, response.Body.String()) } if !strings.Contains(response.Body.String(), "WarpBox Box Manager") { t.Fatal("expected manager UI") } form := url.Values{"disable_zip": []string{"true"}} response = postAccountBoxForm(router, session, "/account/boxes/"+id, form) if response.Code != http.StatusSeeOther { t.Fatalf("expected update redirect, got %d", response.Code) } manifest, err := boxstore.ReadManifest(id) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } if !manifest.DisableZip { t.Fatal("expected sharing rule update") } } func TestAccountBoxManagerOwnerViewAllowedAndDeniedByPolicy(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("owner-view", "owner-view@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "bcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbc" createIndexedBox(t, app, id, user.ID, user.Username, 10, false) response := getAccountBoxManager(router, session, id) if response.Code != http.StatusOK { t.Fatalf("expected owner manager page, got %d", response.Code) } app.config.BoxOwnerEditEnabled = false response = getAccountBoxManager(router, session, id) if response.Code != http.StatusForbidden { t.Fatalf("expected owner denied by policy, got %d", response.Code) } } func TestAccountBoxManagerOwnerRefreshLimits(t *testing.T) { app, _ := setupAccountTestApp(t) app.config.BoxOwnerMaxRefreshCount = 1 app.config.BoxOwnerMaxRefreshAmountSeconds = 60 app.config.BoxOwnerMaxTotalExpirySeconds = 7200 user, err := app.store.CreateUserWithPassword("owner-refresh", "owner-refresh@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" createIndexedBox(t, app, id, user.ID, user.Username, 10, false) response := postAccountBoxForm(router, session, "/account/boxes/"+id+"/extend", url.Values{"extend_seconds": []string{"60"}}) if response.Code != http.StatusSeeOther { t.Fatalf("expected owner refresh success, got %d body=%s", response.Code, response.Body.String()) } record, ok, err := app.store.GetBoxRecord(id) if err != nil || !ok { t.Fatalf("GetBoxRecord returned ok=%v err=%v", ok, err) } if record.RefreshCount != 1 { t.Fatalf("expected refresh count 1, got %d", record.RefreshCount) } response = postAccountBoxForm(router, session, "/account/boxes/"+id+"/extend", url.Values{"extend_seconds": []string{"60"}}) if response.Code != http.StatusOK { t.Fatalf("expected refresh count rejection render, got %d", response.Code) } if !strings.Contains(response.Body.String(), "refresh count") { t.Fatal("expected refresh count error") } } func TestAccountBoxManagerOwnerRefreshRejectedOverMaxDuration(t *testing.T) { app, _ := setupAccountTestApp(t) app.config.BoxOwnerMaxRefreshAmountSeconds = 60 user, err := app.store.CreateUserWithPassword("owner-duration", "owner-duration@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "dededededededededededededededede" createIndexedBox(t, app, id, user.ID, user.Username, 10, false) response := postAccountBoxForm(router, session, "/account/boxes/"+id+"/extend", url.Values{"extend_seconds": []string{"120"}}) if response.Code != http.StatusOK { t.Fatalf("expected max duration rejection render, got %d", response.Code) } if !strings.Contains(response.Body.String(), "maximum single extension") { t.Fatal("expected max duration error") } } func TestAccountBoxManagerPasswordSetRemovePermissions(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("owner-pass", "owner-pass@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "efefefefefefefefefefefefefefefef" createIndexedBox(t, app, id, user.ID, user.Username, 10, false) response := postAccountBoxForm(router, session, "/account/boxes/"+id+"/password", url.Values{"password": []string{"new-secret"}}) if response.Code != http.StatusSeeOther { t.Fatalf("expected password set redirect, got %d", response.Code) } manifest, err := boxstore.ReadManifest(id) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } if manifest.PasswordHash == "" || manifest.AuthToken == "" { t.Fatal("expected password set") } app.config.BoxOwnerPasswordEditEnabled = false response = postAccountBoxForm(router, session, "/account/boxes/"+id+"/password/remove", nil) if response.Code != http.StatusOK { t.Fatalf("expected password permission render, got %d", response.Code) } if !strings.Contains(response.Body.String(), "password editing disabled") { t.Fatal("expected password permission error") } } func TestAccountBoxManagerFileDeleteAndBoxDeletePermissions(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("owner-delete", "owner-delete@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "fafafafafafafafafafafafafafafafa" createIndexedBox(t, app, id, user.ID, user.Username, 10, false) manifest, err := boxstore.ReadManifest(id) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } fileID := manifest.Files[0].ID response := postAccountBoxForm(router, session, "/account/boxes/"+id+"/files/delete", url.Values{"file_ids": []string{fileID}}) if response.Code != http.StatusSeeOther { t.Fatalf("expected file delete redirect, got %d", response.Code) } manifest, err = boxstore.ReadManifest(id) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } if len(manifest.Files) != 0 { t.Fatalf("expected file removed, got %#v", manifest.Files) } app.config.BoxOwnerEditEnabled = false response = postAccountBoxForm(router, session, "/account/boxes/"+id+"/delete", nil) if response.Code != http.StatusForbidden { t.Fatalf("expected delete permission denied after policy disabled, got %d", response.Code) } app.config.BoxOwnerEditEnabled = true response = postAccountBoxForm(router, session, "/account/boxes/"+id+"/delete", nil) if response.Code != http.StatusSeeOther { t.Fatalf("expected box delete redirect, got %d", response.Code) } if _, err := os.Stat(boxstore.BoxPath(id)); !os.IsNotExist(err) { t.Fatalf("expected box directory deleted, stat err=%v", err) } } func getAccountBoxManager(router http.Handler, session metastore.Session, id string) *httptest.ResponseRecorder { request := httptest.NewRequest(http.MethodGet, "/account/boxes/"+id, nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) return response } func postAccountBoxForm(router http.Handler, session metastore.Session, path string, values url.Values) *httptest.ResponseRecorder { if values == nil { values = url.Values{} } values.Set("csrf_token", session.CSRFToken) request := httptest.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) return response }