2020-02-07 17:27:45 +01:00
|
|
|
// Vikunja is a to-do list application to facilitate your life.
|
2020-01-09 18:33:22 +01:00
|
|
|
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
2018-11-26 21:17:33 +01:00
|
|
|
//
|
2019-12-04 20:39:56 +01:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
2018-11-26 21:17:33 +01:00
|
|
|
//
|
2019-12-04 20:39:56 +01:00
|
|
|
// 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 General Public License for more details.
|
2018-11-26 21:17:33 +01:00
|
|
|
//
|
2019-12-04 20:39:56 +01:00
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2018-11-26 21:17:33 +01:00
|
|
|
|
2018-07-12 21:07:59 +02:00
|
|
|
package models
|
|
|
|
|
2018-10-31 13:42:38 +01:00
|
|
|
import (
|
2020-01-26 18:08:06 +01:00
|
|
|
"code.vikunja.io/api/pkg/user"
|
2018-12-01 00:26:56 +01:00
|
|
|
"code.vikunja.io/web"
|
2020-02-14 17:34:25 +01:00
|
|
|
"xorm.io/builder"
|
2018-10-31 13:42:38 +01:00
|
|
|
)
|
|
|
|
|
2018-07-12 21:07:59 +02:00
|
|
|
// CanWrite return whether the user can write on that list or not
|
2019-03-24 13:35:50 +01:00
|
|
|
func (l *List) CanWrite(a web.Auth) (bool, error) {
|
2018-12-01 00:26:56 +01:00
|
|
|
|
2020-09-05 22:16:02 +02:00
|
|
|
// The favorite list can't be edited
|
|
|
|
if l.ID == FavoritesPseudoList.ID {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2019-03-08 22:31:37 +01:00
|
|
|
// Get the list and check the right
|
|
|
|
originalList := &List{ID: l.ID}
|
|
|
|
err := originalList.GetSimpleByID()
|
|
|
|
if err != nil {
|
2019-03-24 13:35:50 +01:00
|
|
|
return false, err
|
2019-03-08 22:31:37 +01:00
|
|
|
}
|
|
|
|
|
2020-03-15 22:50:39 +01:00
|
|
|
// We put the result of the is archived check in a separate variable to be able to return it later without
|
|
|
|
// needing to recheck it again
|
|
|
|
errIsArchived := originalList.CheckIsArchived()
|
|
|
|
|
|
|
|
var canWrite bool
|
|
|
|
|
2019-08-31 22:56:41 +02:00
|
|
|
// Check if we're dealing with a share auth
|
|
|
|
shareAuth, ok := a.(*LinkSharing)
|
|
|
|
if ok {
|
|
|
|
return originalList.ID == shareAuth.ListID &&
|
2020-03-15 22:50:39 +01:00
|
|
|
(shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), errIsArchived
|
2019-08-31 22:56:41 +02:00
|
|
|
}
|
|
|
|
|
2019-01-21 23:08:04 +01:00
|
|
|
// Check if the user is either owner or can write to the list
|
2020-01-26 18:08:06 +01:00
|
|
|
if originalList.isOwner(&user.User{ID: a.GetID()}) {
|
2020-03-15 22:50:39 +01:00
|
|
|
canWrite = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if canWrite {
|
|
|
|
return canWrite, errIsArchived
|
2019-03-24 13:35:50 +01:00
|
|
|
}
|
|
|
|
|
2020-08-10 14:11:43 +02:00
|
|
|
canWrite, _, err = originalList.checkRight(a, RightWrite, RightAdmin)
|
2020-03-15 22:50:39 +01:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return canWrite, errIsArchived
|
2018-07-12 21:07:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// CanRead checks if a user has read access to a list
|
2020-08-10 14:11:43 +02:00
|
|
|
func (l *List) CanRead(a web.Auth) (bool, int, error) {
|
2020-09-05 22:16:02 +02:00
|
|
|
|
|
|
|
// The favorite list needs a special treatment
|
|
|
|
if l.ID == FavoritesPseudoList.ID {
|
|
|
|
owner, err := user.GetFromAuth(a)
|
|
|
|
if err != nil {
|
|
|
|
return false, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
*l = FavoritesPseudoList
|
|
|
|
l.Owner = owner
|
|
|
|
return true, int(RightRead), nil
|
|
|
|
}
|
|
|
|
|
2019-01-21 23:08:04 +01:00
|
|
|
// Check if the user is either owner or can read
|
2019-03-24 14:17:36 +01:00
|
|
|
if err := l.GetSimpleByID(); err != nil {
|
2020-08-10 14:11:43 +02:00
|
|
|
return false, 0, err
|
2019-03-24 14:17:36 +01:00
|
|
|
}
|
2019-08-31 22:56:41 +02:00
|
|
|
|
|
|
|
// Check if we're dealing with a share auth
|
|
|
|
shareAuth, ok := a.(*LinkSharing)
|
|
|
|
if ok {
|
|
|
|
return l.ID == shareAuth.ListID &&
|
2020-08-10 14:11:43 +02:00
|
|
|
(shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), int(shareAuth.Right), nil
|
2019-08-31 22:56:41 +02:00
|
|
|
}
|
|
|
|
|
2020-01-26 18:08:06 +01:00
|
|
|
if l.isOwner(&user.User{ID: a.GetID()}) {
|
2020-08-10 14:11:43 +02:00
|
|
|
return true, int(RightAdmin), nil
|
2019-03-24 13:35:50 +01:00
|
|
|
}
|
2019-06-28 10:21:48 +02:00
|
|
|
return l.checkRight(a, RightRead, RightWrite, RightAdmin)
|
2019-01-21 23:08:04 +01:00
|
|
|
}
|
2018-12-29 15:29:50 +01:00
|
|
|
|
2019-01-21 23:08:04 +01:00
|
|
|
// CanUpdate checks if the user can update a list
|
2020-03-15 22:50:39 +01:00
|
|
|
func (l *List) CanUpdate(a web.Auth) (canUpdate bool, err error) {
|
2020-09-05 22:16:02 +02:00
|
|
|
// The favorite list can't be edited
|
|
|
|
if l.ID == FavoritesPseudoList.ID {
|
|
|
|
return false, nil
|
|
|
|
}
|
2020-03-15 22:50:39 +01:00
|
|
|
canUpdate, err = l.CanWrite(a)
|
|
|
|
// If the list is archived and the user tries to un-archive it, let the request through
|
|
|
|
if IsErrListIsArchived(err) && !l.IsArchived {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
return canUpdate, err
|
2018-07-12 21:07:59 +02:00
|
|
|
}
|
2018-07-12 21:20:24 +02:00
|
|
|
|
|
|
|
// CanDelete checks if the user can delete a list
|
2019-03-24 13:35:50 +01:00
|
|
|
func (l *List) CanDelete(a web.Auth) (bool, error) {
|
2019-01-21 23:08:04 +01:00
|
|
|
return l.IsAdmin(a)
|
2018-07-12 23:07:03 +02:00
|
|
|
}
|
2018-07-12 23:16:32 +02:00
|
|
|
|
2019-08-31 22:56:41 +02:00
|
|
|
// CanCreate checks if the user can create a list
|
2019-03-24 13:35:50 +01:00
|
|
|
func (l *List) CanCreate(a web.Auth) (bool, error) {
|
2020-06-30 22:53:14 +02:00
|
|
|
// A user can create a list if they have write access to the namespace
|
2019-03-08 22:31:37 +01:00
|
|
|
n := &Namespace{ID: l.NamespaceID}
|
2018-12-01 00:26:56 +01:00
|
|
|
return n.CanWrite(a)
|
2018-07-12 23:16:32 +02:00
|
|
|
}
|
2018-07-25 00:40:24 +02:00
|
|
|
|
2019-01-21 23:08:04 +01:00
|
|
|
// IsAdmin returns whether the user has admin rights on the list or not
|
2019-03-24 13:35:50 +01:00
|
|
|
func (l *List) IsAdmin(a web.Auth) (bool, error) {
|
2020-09-05 22:16:02 +02:00
|
|
|
// The favorite list can't be edited
|
|
|
|
if l.ID == FavoritesPseudoList.ID {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2019-03-08 22:31:37 +01:00
|
|
|
originalList := &List{ID: l.ID}
|
|
|
|
err := originalList.GetSimpleByID()
|
|
|
|
if err != nil {
|
2019-03-24 13:35:50 +01:00
|
|
|
return false, err
|
2019-03-08 22:31:37 +01:00
|
|
|
}
|
|
|
|
|
2019-08-31 22:56:41 +02:00
|
|
|
// Check if we're dealing with a share auth
|
|
|
|
shareAuth, ok := a.(*LinkSharing)
|
|
|
|
if ok {
|
|
|
|
return originalList.ID == shareAuth.ListID && shareAuth.Right == RightAdmin, nil
|
|
|
|
}
|
|
|
|
|
2019-01-21 23:08:04 +01:00
|
|
|
// Check all the things
|
|
|
|
// Check if the user is either owner or can write to the list
|
|
|
|
// Owners are always admins
|
2020-01-26 18:08:06 +01:00
|
|
|
if originalList.isOwner(&user.User{ID: a.GetID()}) {
|
2019-03-24 13:35:50 +01:00
|
|
|
return true, nil
|
|
|
|
}
|
2020-08-10 14:11:43 +02:00
|
|
|
is, _, err := originalList.checkRight(a, RightAdmin)
|
|
|
|
return is, err
|
2019-01-21 23:08:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Little helper function to check if a user is list owner
|
2020-01-26 18:08:06 +01:00
|
|
|
func (l *List) isOwner(u *user.User) bool {
|
2019-01-21 23:08:04 +01:00
|
|
|
return l.OwnerID == u.ID
|
2018-07-25 00:40:24 +02:00
|
|
|
}
|
2018-09-06 08:42:18 +02:00
|
|
|
|
2019-01-21 23:08:04 +01:00
|
|
|
// Checks n different rights for any given user
|
2020-08-10 14:11:43 +02:00
|
|
|
func (l *List) checkRight(a web.Auth, rights ...Right) (bool, int, error) {
|
2019-01-21 23:08:04 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
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
|
|
|
|
// If the list was shared directly with the user and the user has the right
|
|
|
|
conds = append(conds, builder.And(
|
2019-06-28 10:21:48 +02:00
|
|
|
builder.Eq{"ul.user_id": a.GetID()},
|
2019-01-21 23:08:04 +01:00
|
|
|
builder.Eq{"ul.right": r},
|
|
|
|
))
|
|
|
|
// If the namespace this list belongs to was shared directly with the user and the user has the right
|
|
|
|
conds = append(conds, builder.And(
|
2019-06-28 10:21:48 +02:00
|
|
|
builder.Eq{"un.user_id": a.GetID()},
|
2019-01-21 23:08:04 +01:00
|
|
|
builder.Eq{"un.right": r},
|
|
|
|
))
|
|
|
|
|
|
|
|
// Team rights
|
|
|
|
// If the list was shared directly with the team and the team has the right
|
|
|
|
conds = append(conds, builder.And(
|
2019-06-28 10:21:48 +02:00
|
|
|
builder.Eq{"tm2.user_id": a.GetID()},
|
2019-01-21 23:08:04 +01:00
|
|
|
builder.Eq{"tl.right": r},
|
|
|
|
))
|
|
|
|
// If the namespace this list belongs to was shared directly with the team and the team has the right
|
|
|
|
conds = append(conds, builder.And(
|
2019-06-28 10:21:48 +02:00
|
|
|
builder.Eq{"tm.user_id": a.GetID()},
|
2019-01-21 23:08:04 +01:00
|
|
|
builder.Eq{"tn.right": r},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2019-08-14 22:19:04 +02:00
|
|
|
// If the user is the owner of a namespace, it has any right, all the time
|
|
|
|
conds = append(conds, builder.Eq{"n.owner_id": a.GetID()})
|
|
|
|
|
2020-08-10 14:11:43 +02:00
|
|
|
type allListRights struct {
|
|
|
|
UserNamespace NamespaceUser `xorm:"extends"`
|
|
|
|
UserList ListUser `xorm:"extends"`
|
|
|
|
|
|
|
|
TeamNamespace TeamNamespace `xorm:"extends"`
|
|
|
|
TeamList TeamList `xorm:"extends"`
|
|
|
|
}
|
|
|
|
|
|
|
|
r := &allListRights{}
|
|
|
|
var maxRight = 0
|
|
|
|
exists, err := x.
|
2018-09-06 08:42:18 +02:00
|
|
|
Table("list").
|
|
|
|
Alias("l").
|
2019-01-21 23:08:04 +01:00
|
|
|
// User stuff
|
2018-09-06 08:42:18 +02:00
|
|
|
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
|
|
|
|
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
|
2018-09-12 19:56:07 +02:00
|
|
|
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
|
2019-01-21 23:08:04 +01:00
|
|
|
// 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},
|
|
|
|
)).
|
2020-08-10 14:11:43 +02:00
|
|
|
Get(r)
|
|
|
|
|
|
|
|
// Figure out the max right and return it
|
|
|
|
if int(r.UserNamespace.Right) > maxRight {
|
|
|
|
maxRight = int(r.UserNamespace.Right)
|
|
|
|
}
|
|
|
|
if int(r.UserList.Right) > maxRight {
|
|
|
|
maxRight = int(r.UserList.Right)
|
|
|
|
}
|
|
|
|
if int(r.TeamNamespace.Right) > maxRight {
|
|
|
|
maxRight = int(r.TeamNamespace.Right)
|
|
|
|
}
|
|
|
|
if int(r.TeamList.Right) > maxRight {
|
|
|
|
maxRight = int(r.TeamList.Right)
|
|
|
|
}
|
|
|
|
|
|
|
|
return exists, maxRight, err
|
2018-09-06 08:42:18 +02:00
|
|
|
}
|