fix(auth): allow api tokens to access global v2 task list endpoint
The tasks.read_all special case in CanDoAPIRoute only covered v1 paths. Both GET /api/v2/tasks and GET /api/v2/projects/:project/tasks normalize to the same tasks.read_all map key, but only one RouteDetail survives — the project-scoped path overwrites the global one. The exact path comparison then rejects the global endpoint with 401. Extend the special case to include the v2 paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7a182817ee
commit
f8eacca7c8
|
|
@ -427,7 +427,8 @@ func CanDoAPIRoute(c *echo.Context, token *APIToken) (can bool) {
|
||||||
// Two list endpoints share tasks.read_all but only one
|
// Two list endpoints share tasks.read_all but only one
|
||||||
// survives collection, so allow either explicitly.
|
// survives collection, so allow either explicitly.
|
||||||
if group == "tasks" && p == "read_all" && method == http.MethodGet &&
|
if group == "tasks" && p == "read_all" && method == http.MethodGet &&
|
||||||
(path == "/api/v1/tasks" || path == "/api/v1/projects/:project/tasks") {
|
(path == "/api/v1/tasks" || path == "/api/v1/projects/:project/tasks" ||
|
||||||
|
path == "/api/v2/tasks" || path == "/api/v2/projects/:project/tasks") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,40 @@ func TestCanDoAPIRoute_V2PatchAliasesPut(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCanDoAPIRoute_V2TasksReadAll verifies that tasks.read_all authorises
|
||||||
|
// both the global /api/v2/tasks and project-scoped /api/v2/projects/:project/tasks
|
||||||
|
// endpoints. Both normalise to tasks.read_all via getRouteGroupName, but only
|
||||||
|
// one RouteDetail survives in the map — the special case in CanDoAPIRoute must
|
||||||
|
// accept either path.
|
||||||
|
func TestCanDoAPIRoute_V2TasksReadAll(t *testing.T) {
|
||||||
|
apiTokenRoutes = make(map[string]APITokenRoute)
|
||||||
|
apiTokenRoutesV2 = make(map[string]APITokenRoute)
|
||||||
|
apiTokenRoutes["caldav"] = APITokenRoute{
|
||||||
|
"access": &RouteDetail{Path: "/dav/*", Method: "ANY"},
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/tasks"}, true)
|
||||||
|
CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/projects/:project/tasks"}, true)
|
||||||
|
|
||||||
|
token := &APIToken{
|
||||||
|
APIPermissions: APIPermissions{"tasks": []string{"read_all"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
t.Run("global /api/v2/tasks", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest("GET", "/api/v2/tasks", nil)
|
||||||
|
c := e.NewContext(req, httptest.NewRecorder())
|
||||||
|
assert.True(t, CanDoAPIRoute(c, token))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("project-scoped /api/v2/projects/:project/tasks", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest("GET", "/api/v2/projects/:project/tasks", nil)
|
||||||
|
c := e.NewContext(req, httptest.NewRecorder())
|
||||||
|
assert.True(t, CanDoAPIRoute(c, token))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// End-to-end CanDoAPIRoute coverage for /api/v2 is provided by the Label
|
// End-to-end CanDoAPIRoute coverage for /api/v2 is provided by the Label
|
||||||
// integration test in pkg/webtests/huma_label_test.go (see the token-auth
|
// integration test in pkg/webtests/huma_label_test.go (see the token-auth
|
||||||
// scenarios in that file) which exercises the full auth pipeline.
|
// scenarios in that file) which exercises the full auth pipeline.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue