feat(api tokens): add tests

This commit is contained in:
kolaente 2023-09-01 10:19:31 +02:00
parent e3dac16398
commit d9bfcdab8e
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
7 changed files with 283 additions and 11 deletions

View File

@ -0,0 +1,33 @@
- id: 1
title: 'test token 1'
token_salt: iC1Qbpf7H1
token_hash: a1813a558185d99f5197d2d549e4dd91292376aa00210229d70f77b57e165f6613fd12c1f790aa6493548cb9bceff33b45b4
token_last_eight: 75f29d2e
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2099-01-01 00:00:00
owner_id: 1
created: 2023-09-01 07:00:00
updated: 2023-09-01 07:00:00
# token in plaintext is tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e
- id: 2
title: 'test token 2'
token_salt: EtwMsqDfOA
token_hash: 5c4d80c58947f21295064d473937709f1159ab09085eb59e38783da6032181069ec2e1d236486533b66999f9f4ac375b45f5
token_last_eight: 235008c8
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2023-01-01 00:00:00
owner_id: 1
created: 2023-09-01 07:00:00
updated: 2023-09-01 07:00:00
# token in plaintext is tk_a5e6f92ddbad68f49ee2c63e52174db0235008c8
- id: 3
title: 'test token 3'
token_salt: AHeetyp1aB
token_hash: da4b9c3aa72633274c37ab3419fbfbe4c5b79310b76027ac36f85e4c5ad0c2342a1d9e1c9b72ca07ec0a66ad2ee3505539af
token_last_eight: 0b8dcb7c
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2099-01-01 00:00:00
owner_id: 2
created: 2023-09-01 07:00:00
updated: 2023-09-01 07:00:00
# token in plaintext is tk_5e29ae2ae079781ff73b0a3e0fe4d75a0b8dcb7c

View File

@ -0,0 +1,113 @@
// 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 Licensee 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 Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"net/http"
"net/http/httptest"
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestAPIToken(t *testing.T) {
t.Run("valid token", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
u, err := auth.GetAuthFromClaims(c)
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, u)
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e") // Token 1
assert.NoError(t, h(c))
// check if the request handlers "see" the request as if it came directly from that user
assert.Contains(t, res.Body.String(), `"username":"user1"`)
})
t.Run("invalid token", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_loremipsumdolorsitamet")
assert.Error(t, h(c))
})
t.Run("expired token", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_a5e6f92ddbad68f49ee2c63e52174db0235008c8") // Token 2
assert.Error(t, h(c))
})
t.Run("valid token, invalid scope", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e")
assert.Error(t, h(c))
})
t.Run("jwt", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
s := db.NewSession()
defer s.Close()
u, err := user.GetUserByID(s, 1)
assert.NoError(t, err)
jwt, err := auth.NewUserJWTAuthtoken(u, false)
assert.NoError(t, err)
req.Header.Set(echo.HeaderAuthorization, "Bearer "+jwt)
assert.NoError(t, h(c))
})
}

View File

@ -129,7 +129,8 @@ func GetAvailableAPIRoutesForToken(c echo.Context) error {
// CanDoAPIRoute checks if a token is allowed to use the current api route
func CanDoAPIRoute(c echo.Context, token *APIToken) (can bool) {
routeGroupName := getRouteGroupName(c.Path())
path := c.Request().URL.Path
routeGroupName := getRouteGroupName(path)
group, hasGroup := token.Permissions[routeGroupName]
if !hasGroup {
@ -142,19 +143,19 @@ func CanDoAPIRoute(c echo.Context, token *APIToken) (can bool) {
return false
}
if routes.Create != nil && routes.Create.Path == c.Path() && routes.Create.Method == c.Request().Method {
if routes.Create != nil && routes.Create.Path == path && routes.Create.Method == c.Request().Method {
route = "create"
}
if routes.ReadOne != nil && routes.ReadOne.Path == c.Path() && routes.ReadOne.Method == c.Request().Method {
if routes.ReadOne != nil && routes.ReadOne.Path == path && routes.ReadOne.Method == c.Request().Method {
route = "read_one"
}
if routes.ReadAll != nil && routes.ReadAll.Path == c.Path() && routes.ReadAll.Method == c.Request().Method {
if routes.ReadAll != nil && routes.ReadAll.Path == path && routes.ReadAll.Method == c.Request().Method {
route = "read_all"
}
if routes.Update != nil && routes.Update.Path == c.Path() && routes.Update.Method == c.Request().Method {
if routes.Update != nil && routes.Update.Path == path && routes.Update.Method == c.Request().Method {
route = "update"
}
if routes.Delete != nil && routes.Delete.Path == c.Path() && routes.Delete.Method == c.Request().Method {
if routes.Delete != nil && routes.Delete.Path == path && routes.Delete.Method == c.Request().Method {
route = "delete"
}

View File

@ -21,6 +21,7 @@ import (
"crypto/subtle"
"encoding/hex"
"time"
"xorm.io/builder"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/utils"
@ -132,19 +133,24 @@ func (t *APIToken) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
tokens := []*APIToken{}
query := s.Where("owner_id = ?", a.GetID()).
Limit(getLimitFromPageIndex(page, perPage))
var where builder.Cond = builder.Eq{"owner_id": a.GetID()}
if search != "" {
query = query.Where(db.ILIKE("title", search))
where = builder.And(
where,
db.ILIKE("title", search),
)
}
err = query.Find(&tokens)
err = s.
Where(where).
Limit(getLimitFromPageIndex(page, perPage)).
Find(&tokens)
if err != nil {
return nil, 0, 0, err
}
totalCount, err := query.Count(&APIToken{})
totalCount, err := s.Where(where).Count(&APIToken{})
return tokens, len(tokens), totalCount, err
}

View File

@ -0,0 +1,117 @@
// 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 Licensee 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 Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import (
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
)
func TestAPIToken_ReadAll(t *testing.T) {
u := &user.User{ID: 1}
token := &APIToken{}
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
// Checking if the user only sees their own tokens
result, count, total, err := token.ReadAll(s, u, "", 1, 50)
assert.NoError(t, err)
tokens, is := result.([]*APIToken)
assert.Truef(t, is, "tokens are not of type []*APIToken")
assert.Len(t, tokens, 2)
assert.Equal(t, count, len(tokens))
assert.Equal(t, int64(2), total)
assert.Equal(t, int64(1), tokens[0].ID)
assert.Equal(t, int64(2), tokens[1].ID)
}
func TestAPIToken_CanDelete(t *testing.T) {
t.Run("own token", func(t *testing.T) {
u := &user.User{ID: 1}
token := &APIToken{ID: 1}
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
can, err := token.CanDelete(s, u)
assert.NoError(t, err)
assert.True(t, can)
})
t.Run("noneixsting token", func(t *testing.T) {
u := &user.User{ID: 1}
token := &APIToken{ID: 999}
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
can, err := token.CanDelete(s, u)
assert.NoError(t, err)
assert.False(t, can)
})
t.Run("token of another user", func(t *testing.T) {
u := &user.User{ID: 2}
token := &APIToken{ID: 1}
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
can, err := token.CanDelete(s, u)
assert.NoError(t, err)
assert.False(t, can)
})
}
func TestAPIToken_Create(t *testing.T) {
t.Run("normal", func(t *testing.T) {
u := &user.User{ID: 1}
token := &APIToken{}
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
err := token.Create(s, u)
})
}
func TestAPIToken_GetTokenFromTokenString(t *testing.T) {
t.Run("valid token", func(t *testing.T) {
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
token, err := GetTokenFromTokenString(s, "tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e") // Token 1
assert.NoError(t, err)
assert.Equal(t, int64(1), token.ID)
})
t.Run("invalid token", func(t *testing.T) {
s := db.NewSession()
defer s.Close()
db.LoadAndAssertFixtures(t)
_, err := GetTokenFromTokenString(s, "tk_loremipsum")
assert.Error(t, err)
assert.True(t, IsErrAPITokenInvalid(err))
})
}

View File

@ -58,6 +58,7 @@ func GetTables() []interface{} {
&SavedFilter{},
&Subscription{},
&Favorite{},
&APIToken{},
}
}

View File

@ -64,6 +64,7 @@ func SetupTests() {
"saved_filters",
"subscriptions",
"favorites",
"api_tokens",
)
if err != nil {
log.Fatal(err)