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
|
||||
// survives collection, so allow either explicitly.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// integration test in pkg/webtests/huma_label_test.go (see the token-auth
|
||||
// scenarios in that file) which exercises the full auth pipeline.
|
||||
|
|
|
|||
Loading…
Reference in New Issue