feat(api tokens): check permissions when saving

This commit is contained in:
kolaente 2023-09-01 08:52:57 +02:00
parent e4c71123ef
commit e3dac16398
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
4 changed files with 85 additions and 10 deletions

View File

@ -14,14 +14,12 @@
// 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 routes
package models
import (
"net/http"
"strings"
"code.vikunja.io/api/pkg/models"
"github.com/labstack/echo/v4"
)
@ -64,8 +62,8 @@ func getRouteGroupName(path string) string {
}
}
// collectRoutesForAPITokenUsage gets called for every added APITokenRoute and builds a list of all routes we can use for the api tokens.
func collectRoutesForAPITokenUsage(route echo.Route) {
// CollectRoutesForAPITokenUsage gets called for every added APITokenRoute and builds a list of all routes we can use for the api tokens.
func CollectRoutesForAPITokenUsage(route echo.Route) {
if !strings.Contains(route.Name, "(*WebHandler)") {
return
@ -130,7 +128,7 @@ 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 *models.APIToken) (can bool) {
func CanDoAPIRoute(c echo.Context, token *APIToken) (can bool) {
routeGroupName := getRouteGroupName(c.Path())
group, hasGroup := token.Permissions[routeGroupName]
@ -168,3 +166,50 @@ func CanDoAPIRoute(c echo.Context, token *models.APIToken) (can bool) {
return false
}
func PermissionsAreValid(permissions APIPermissions) (err error) {
for key, methods := range permissions {
routes, has := apiTokenRoutes[key]
if !has {
return &ErrInvalidAPITokenPermission{
Group: key,
}
}
for _, method := range methods {
if method == "create" && routes.Create == nil {
return &ErrInvalidAPITokenPermission{
Group: key,
Permission: method,
}
}
if method == "read_one" && routes.ReadOne == nil {
return &ErrInvalidAPITokenPermission{
Group: key,
Permission: method,
}
}
if method == "read_all" && routes.ReadAll == nil {
return &ErrInvalidAPITokenPermission{
Group: key,
Permission: method,
}
}
if method == "update" && routes.Update == nil {
return &ErrInvalidAPITokenPermission{
Group: key,
Permission: method,
}
}
if method == "delete" && routes.Delete == nil {
return &ErrInvalidAPITokenPermission{
Group: key,
Permission: method,
}
}
}
}
return nil
}

View File

@ -102,7 +102,9 @@ func (t *APIToken) Create(s *xorm.Session, a web.Auth) (err error) {
t.OwnerID = a.GetID()
// TODO: validate permissions
if err := PermissionsAreValid(t.Permissions); err != nil {
return err
}
_, err = s.Insert(t)
return err

View File

@ -1685,3 +1685,31 @@ func (err ErrAPITokenInvalid) HTTPError() web.HTTPError {
Message: "The provided api token is invalid.",
}
}
// ErrInvalidAPITokenPermission represents an error where an api token is invalid
type ErrInvalidAPITokenPermission struct {
Group string
Permission string
}
// IsErrInvalidAPITokenPermission checks if an error is ErrInvalidAPITokenPermission.
func IsErrInvalidAPITokenPermission(err error) bool {
_, ok := err.(*ErrInvalidAPITokenPermission)
return ok
}
func (err *ErrInvalidAPITokenPermission) Error() string {
return fmt.Sprintf("API token permission %s of group %s is invalid", err.Permission, err.Group)
}
// ErrCodeInvalidAPITokenPermission holds the unique world-error code of this error
const ErrCodeInvalidAPITokenPermission = 14002
// HTTPError holds the http error description
func (err ErrInvalidAPITokenPermission) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeInvalidAPITokenPermission,
Message: fmt.Sprintf("The permission %s of group %s is invalid.", err.Permission, err.Group),
}
}

View File

@ -204,7 +204,7 @@ func RegisterRoutes(e *echo.Echo) {
// API Routes
a := e.Group("/api/v1")
e.OnAddRouteHandler = func(host string, route echo.Route, handler echo.HandlerFunc, middleware []echo.MiddlewareFunc) {
collectRoutesForAPITokenUsage(route)
models.CollectRoutesForAPITokenUsage(route)
}
registerAPIRoutes(a)
}
@ -316,7 +316,7 @@ func registerAPIRoutes(a *echo.Group) {
return echo.NewHTTPError(http.StatusUnauthorized)
}
if !CanDoAPIRoute(c, token) {
if !models.CanDoAPIRoute(c, token) {
return echo.NewHTTPError(http.StatusUnauthorized)
}
@ -333,7 +333,7 @@ func registerAPIRoutes(a *echo.Group) {
setupMetricsMiddleware(a)
a.POST("/tokenTest", apiv1.CheckToken)
a.GET("/routes", GetAvailableAPIRoutesForToken)
a.GET("/routes", models.GetAvailableAPIRoutesForToken)
// User stuff
u := a.Group("/user")