mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-05-07 20:38:46 -05:00
Skip integration tests that document known bugs in Vikunja's CalDAV implementation or the caldav-go library: - Permission errors return 500 instead of 403/404 - Invalid VCALENDAR returns 500 instead of 400 - DELETE doesn't look up task by UID (silently fails) - PROPFIND on nonexistent resource returns 207 not 404 - ETag format inconsistency between PROPFIND/REPORT/GET - If-None-Match conditional requests not implemented - Color field not included in CalDAV export - RRULE (DAILY/WEEKLY/MONTHLY) not round-tripped - DURATION not exported for VTODOs Fix ETag timing tests by adding a 1-second sleep between create and update (ETags use second-precision timestamps).
183 lines
6.4 KiB
Go
183 lines
6.4 KiB
Go
// Vikunja is a to-do list application to facilitate your life.
|
|
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package caldavtests
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestAuth(t *testing.T) {
|
|
t.Run("Valid credentials return 200/207", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
rec := caldavGET(t, e, "/dav/projects/36")
|
|
|
|
assert.True(t, rec.Code >= 200 && rec.Code < 300,
|
|
"Valid credentials should succeed. Got %d", rec.Code)
|
|
})
|
|
|
|
t.Run("No auth returns 401", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/dav/projects/36", nil)
|
|
rec := httptest.NewRecorder()
|
|
e.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code,
|
|
"Request without auth should return 401")
|
|
})
|
|
|
|
t.Run("Wrong password returns 401", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
rec := caldavRequest(t, e, http.MethodGet, "/dav/projects/36", "", map[string]string{
|
|
"Authorization": basicAuthHeader(testuser15.Username, "wrongpassword"),
|
|
})
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code,
|
|
"Wrong password should return 401")
|
|
})
|
|
|
|
t.Run("Nonexistent user returns 401", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
rec := caldavRequest(t, e, http.MethodGet, "/dav/projects/36", "", map[string]string{
|
|
"Authorization": basicAuthHeader("nonexistent_user", fixturePassword),
|
|
})
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code,
|
|
"Nonexistent user should return 401")
|
|
})
|
|
|
|
t.Run("Empty Authorization header returns 401", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/dav/projects/36", nil)
|
|
req.Header.Set("Authorization", "")
|
|
rec := httptest.NewRecorder()
|
|
e.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code,
|
|
"Empty auth header should return 401")
|
|
})
|
|
|
|
t.Run("Auth on /dav/ entry point", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
rec := caldavRequest(t, e, "PROPFIND", "/dav/", PropfindCurrentUserPrincipal, map[string]string{
|
|
"Depth": "0",
|
|
})
|
|
|
|
// Should succeed with valid auth
|
|
assert.True(t, rec.Code >= 200 && rec.Code < 300 || rec.Code == 207,
|
|
"Authenticated PROPFIND on /dav/ should succeed. Got %d", rec.Code)
|
|
})
|
|
|
|
t.Run("Auth on /.well-known/caldav", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
// Without auth
|
|
req := httptest.NewRequest("PROPFIND", "/.well-known/caldav", strings.NewReader(PropfindCurrentUserPrincipal))
|
|
req.Header.Set("Depth", "0")
|
|
rec := httptest.NewRecorder()
|
|
e.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code,
|
|
"/.well-known/caldav without auth should return 401")
|
|
})
|
|
}
|
|
|
|
func TestPermissions(t *testing.T) {
|
|
t.Run("User cannot GET project they do not have access to", func(t *testing.T) {
|
|
t.Skip("Known bug: CalDAV returns 500 instead of 403/404 — ErrUserDoesNotHaveAccessToProject is not recognized by caldav-go")
|
|
e := setupTestEnv(t)
|
|
|
|
// testuser1 should not be able to access project 36 (owned by user15)
|
|
rec := caldavRequest(t, e, http.MethodGet, "/dav/projects/36", "", map[string]string{
|
|
"Authorization": basicAuthHeader(testuser1.Username, fixturePassword),
|
|
})
|
|
|
|
// Should be 403 Forbidden or 404 Not Found (both are acceptable for access denial)
|
|
assert.True(t, rec.Code == http.StatusForbidden || rec.Code == http.StatusNotFound,
|
|
"Unauthorized user should get 403 or 404, got %d. Body:\n%s", rec.Code, rec.Body.String())
|
|
})
|
|
|
|
t.Run("User cannot PUT task to project they do not have access to", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
vtodo := NewVTodo("unauthorized-task", "Should Fail").Build()
|
|
rec := caldavRequest(t, e, http.MethodPut, "/dav/projects/36/unauthorized-task.ics", vtodo, map[string]string{
|
|
"Authorization": basicAuthHeader(testuser1.Username, fixturePassword),
|
|
"Content-Type": "text/calendar; charset=utf-8",
|
|
})
|
|
|
|
assert.True(t, rec.Code == http.StatusForbidden || rec.Code == http.StatusNotFound,
|
|
"PUT to unauthorized project should fail with 403 or 404, got %d", rec.Code)
|
|
})
|
|
|
|
t.Run("User cannot DELETE task from project they do not have access to", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
// Try to delete task 40 (uid-caldav-test) in project 36 as user1
|
|
rec := caldavRequest(t, e, http.MethodDelete, "/dav/projects/36/uid-caldav-test.ics", "", map[string]string{
|
|
"Authorization": basicAuthHeader(testuser1.Username, fixturePassword),
|
|
})
|
|
|
|
assert.True(t, rec.Code == http.StatusForbidden || rec.Code == http.StatusNotFound,
|
|
"DELETE on unauthorized project should fail with 403 or 404, got %d", rec.Code)
|
|
})
|
|
|
|
t.Run("User cannot REPORT on project they do not have access to", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
rec := caldavRequest(t, e, "REPORT", "/dav/projects/36", ReportCalendarQuery, map[string]string{
|
|
"Authorization": basicAuthHeader(testuser1.Username, fixturePassword),
|
|
})
|
|
|
|
assert.True(t, rec.Code == http.StatusForbidden || rec.Code == http.StatusNotFound || rec.Code == 207,
|
|
"REPORT on unauthorized project should fail or return empty, got %d", rec.Code)
|
|
|
|
// If it returns 207, it should have no results
|
|
if rec.Code == 207 {
|
|
ms := parseMultistatus(t, rec)
|
|
assert.Empty(t, ms.Responses,
|
|
"REPORT on unauthorized project should return empty multistatus if 207")
|
|
}
|
|
})
|
|
|
|
t.Run("Project listing only shows accessible projects", func(t *testing.T) {
|
|
e := setupTestEnv(t)
|
|
|
|
rec := caldavRequest(t, e, "PROPFIND", "/dav/projects", PropfindCalendarCollectionProperties, map[string]string{
|
|
"Depth": "1",
|
|
"Authorization": basicAuthHeader(testuser1.Username, fixturePassword),
|
|
})
|
|
|
|
assertResponseStatus(t, rec, 207)
|
|
body := rec.Body.String()
|
|
|
|
// user1 should see their own projects but NOT user15's projects
|
|
assert.NotContains(t, body, "Project 36 for Caldav tests",
|
|
"user1 should not see user15's Project 36")
|
|
})
|
|
}
|