feat(api tokens): check permissions when saving
This commit is contained in:
parent
e4c71123ef
commit
e3dac16398
@ -14,14 +14,12 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public Licensee
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package routes
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/models"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"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.
|
// 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) {
|
func CollectRoutesForAPITokenUsage(route echo.Route) {
|
||||||
|
|
||||||
if !strings.Contains(route.Name, "(*WebHandler)") {
|
if !strings.Contains(route.Name, "(*WebHandler)") {
|
||||||
return
|
return
|
||||||
@ -130,7 +128,7 @@ func GetAvailableAPIRoutesForToken(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CanDoAPIRoute checks if a token is allowed to use the current api route
|
// 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())
|
routeGroupName := getRouteGroupName(c.Path())
|
||||||
|
|
||||||
group, hasGroup := token.Permissions[routeGroupName]
|
group, hasGroup := token.Permissions[routeGroupName]
|
||||||
@ -168,3 +166,50 @@ func CanDoAPIRoute(c echo.Context, token *models.APIToken) (can bool) {
|
|||||||
|
|
||||||
return false
|
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
|
||||||
|
}
|
@ -102,7 +102,9 @@ func (t *APIToken) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||||||
|
|
||||||
t.OwnerID = a.GetID()
|
t.OwnerID = a.GetID()
|
||||||
|
|
||||||
// TODO: validate permissions
|
if err := PermissionsAreValid(t.Permissions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = s.Insert(t)
|
_, err = s.Insert(t)
|
||||||
return err
|
return err
|
||||||
|
@ -1685,3 +1685,31 @@ func (err ErrAPITokenInvalid) HTTPError() web.HTTPError {
|
|||||||
Message: "The provided api token is invalid.",
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -204,7 +204,7 @@ func RegisterRoutes(e *echo.Echo) {
|
|||||||
// API Routes
|
// API Routes
|
||||||
a := e.Group("/api/v1")
|
a := e.Group("/api/v1")
|
||||||
e.OnAddRouteHandler = func(host string, route echo.Route, handler echo.HandlerFunc, middleware []echo.MiddlewareFunc) {
|
e.OnAddRouteHandler = func(host string, route echo.Route, handler echo.HandlerFunc, middleware []echo.MiddlewareFunc) {
|
||||||
collectRoutesForAPITokenUsage(route)
|
models.CollectRoutesForAPITokenUsage(route)
|
||||||
}
|
}
|
||||||
registerAPIRoutes(a)
|
registerAPIRoutes(a)
|
||||||
}
|
}
|
||||||
@ -316,7 +316,7 @@ func registerAPIRoutes(a *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusUnauthorized)
|
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !CanDoAPIRoute(c, token) {
|
if !models.CanDoAPIRoute(c, token) {
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized)
|
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +333,7 @@ func registerAPIRoutes(a *echo.Group) {
|
|||||||
setupMetricsMiddleware(a)
|
setupMetricsMiddleware(a)
|
||||||
|
|
||||||
a.POST("/tokenTest", apiv1.CheckToken)
|
a.POST("/tokenTest", apiv1.CheckToken)
|
||||||
a.GET("/routes", GetAvailableAPIRoutesForToken)
|
a.GET("/routes", models.GetAvailableAPIRoutesForToken)
|
||||||
|
|
||||||
// User stuff
|
// User stuff
|
||||||
u := a.Group("/user")
|
u := a.Group("/user")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user