Rights performance improvements for lists and namespaces #54

Merged
konrad merged 14 commits from improvement/rights-performance into master 2019-01-21 22:08:05 +00:00
6 changed files with 120 additions and 3 deletions
Showing only changes of commit cdb63d33af - Show all commits

View File

@ -4,7 +4,7 @@ Content-Type: application/json
{
"username": "user",
"password": "1234"
"password": "jup2000"
}
> {% client.global.set("auth_token", response.body.token); %}

View File

@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}}
###
# Get one list
GET http://localhost:8080/api/v1/lists/1172
GET http://localhost:8080/api/v1/lists/1
Authorization: Bearer {{auth_token}}
###

View File

@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}}
###
# Get one namespaces
GET http://localhost:8080/api/v1/namespaces/125476
GET http://localhost:8080/api/v1/namespaces/-1
Authorization: Bearer {{auth_token}}
###

View File

@ -24,6 +24,7 @@ This document describes the different errors Vikunja can return.
| 4003 | 403 | All bulk editing tasks must belong to the same list. |
| 4004 | 403 | Need at least one task when bulk editing tasks. |
| 4005 | 403 | The user does not have the right to see the task. |
| 4006 | 403 | The list right is invalid. |
| 5001 | 404 | The namspace does not exist. |
| 5003 | 403 | The user does not have access to the specified namespace. |
| 5006 | 400 | The namespace name cannot be empty. |

View File

@ -502,6 +502,29 @@ func (err ErrNoRightToSeeTask) HTTPError() web.HTTPError {
}
}
// ErrInvalidListRight represents an error where a list right is invalid
type ErrInvalidListRight struct {
Right ListRight
}
// IsErrInvalidListRight checks if an error is ErrInvalidListRight.
func IsErrInvalidListRight(err error) bool {
_, ok := err.(ErrInvalidListRight)
return ok
}
func (err ErrInvalidListRight) Error() string {
return fmt.Sprintf("List right is invalid [Right: %d]", err.Right)
}
// ErrCodeInvalidListRight holds the unique world-error code of this error
const ErrCodeInvalidListRight = 4006
// HTTPError holds the http error description
func (err ErrInvalidListRight) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidListRight, Message: "The list right is invalid."}
}
// =================
// Namespace errors
// =================

View File

@ -19,6 +19,7 @@ package models
import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/web"
"github.com/go-xorm/builder"
)
// IsAdmin returns whether the user has admin rights on the list or not
@ -135,3 +136,95 @@ func (l *List) checkListUserRight(user *User, r UserRight) bool {
return exists
}
func (l *List) checkListRight(user *User, rights ...UserRight) bool {
/*
The following loop creates an sql condition like this one:
(ul.user_id = 1 AND ul.right = 1) OR (un.user_id = 1 AND un.right = 1) OR
(tm.user_id = 1 AND tn.right = 1) OR (tm2.user_id = 1 AND tl.right = 1) OR
for each passed right. That way, we can check with a single sql query (instead if 8)
if the user has the right to see the list or not.
*/
var conds []builder.Cond
for _, r := range rights {
// User conditions
conds = append(conds, builder.Or(
builder.And(
builder.Eq{"ul.user_id": user.ID},
builder.Eq{"ul.right": r},
),
builder.And(
builder.Eq{"un.user_id": user.ID},
builder.Eq{"un.right": r},
),
))
// Team rights
conds = append(conds, builder.Or(
builder.And(
builder.Eq{"tm.user_id": user.ID},
builder.Eq{"tn.right": r},
),
builder.And(
builder.Eq{"tm2.user_id": user.ID},
builder.Eq{"tl.right": r},
),
))
}
exists, err := x.Select("l.*").
Table("list").
Alias("l").
// User stuff
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
// Team stuff
Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
// The actual condition
Where(builder.And(
builder.Or(
conds...,
),
builder.Eq{"l.id": l.ID},
)).
Exist(&List{})
if err != nil {
log.Log.Error("Error occurred during checkListUserRight for List: %s", err)
return false
}
return exists
}
// ListRight defines the rights users/teams can have for lists
type ListRight int
// define unknown list right
const (
ListRightUnknown = -1
)
// Enumerate all the list rights
const (
// Can read lists
ListRightRead ListRight = iota
// Can write/edit tasks in a list
ListRightWrite
// Can manage a list, can do everything
ListRightAdmin
)
func (r ListRight) isValid() error {
if r != ListRightAdmin && r != ListRightRead && r != ListRightWrite {
return ErrInvalidListRight{r}
}
return nil
}