Return rights when reading a single item #626
3
go.mod
3
go.mod
|
@ -18,7 +18,7 @@ module code.vikunja.io/api
|
|||
|
||||
require (
|
||||
4d63.com/tz v1.1.0
|
||||
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a
|
||||
code.vikunja.io/web v0.0.0-20200809154828-8767618f181f
|
||||
gitea.com/xorm/xorm-redis-cache v0.2.0
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
||||
|
@ -69,7 +69,6 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
|
|
13
go.sum
13
go.sum
|
@ -19,6 +19,10 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
|||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a h1:RiLIcnTTBP43QlL7nL0ko+PkzaBUCp7NmgogPeZBx5I=
|
||||
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a/go.mod h1:q3to9xazLf9XoqIRk1Y+YCjGr5TYgpQFNSVclCKrmEQ=
|
||||
code.vikunja.io/web v0.0.0-20200809150710-7e12686f28b9 h1:NWXOCZ+FI9pXwpBNISsZil9erTEn25AVfLKcw/J0Sw4=
|
||||
code.vikunja.io/web v0.0.0-20200809150710-7e12686f28b9/go.mod h1:vDWiCtftF6LNCCrem7mjstPWMgzLUvMW/L4YwIQ1Voo=
|
||||
code.vikunja.io/web v0.0.0-20200809154828-8767618f181f h1:Zgtk9lbJkGbKjdTC78mg/c2uNkesxDJs1YUIL9zGvco=
|
||||
code.vikunja.io/web v0.0.0-20200809154828-8767618f181f/go.mod h1:vDWiCtftF6LNCCrem7mjstPWMgzLUvMW/L4YwIQ1Voo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
|
@ -441,6 +445,8 @@ github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx
|
|||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
@ -663,6 +669,8 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W
|
|||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
|
||||
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.0 h1:y3yXRCoDvC2HTtIHvL2cc7Zd+bqA+zqDO6oQzsJO07E=
|
||||
github.com/valyala/fasttemplate v1.2.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
|
@ -705,6 +713,7 @@ golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -770,6 +779,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
|
@ -821,6 +832,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 h1:yi1hN8dcqI9l8klZfy4B8mJvFmmAxJEePIQQFNSd7Cs=
|
||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
@ -75,6 +75,7 @@ func TestList(t *testing.T) {
|
|||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1,"username":"user1",`)
|
||||
assert.NotContains(t, rec.Body.String(), `"owner":{"id":2,"username":"user2",`)
|
||||
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) // User 1 is owner so they should have admin rights.
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9999"})
|
||||
|
@ -84,72 +85,85 @@ func TestList(t *testing.T) {
|
|||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
assert.Empty(t, rec.Result().Header.Get("x-max-rights"))
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "6"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test6"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "7"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test7"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test8"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test9"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test10"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test11"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "12"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "15"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -33,7 +33,7 @@ func (l *Label) CanDelete(a web.Auth) (bool, error) {
|
|||
}
|
||||
|
||||
// CanRead checks if a user can read a label
|
||||
func (l *Label) CanRead(a web.Auth) (bool, error) {
|
||||
func (l *Label) CanRead(a web.Auth) (bool, int, error) {
|
||||
return l.hasAccessToLabel(a)
|
||||
}
|
||||
|
||||
|
@ -61,24 +61,37 @@ func (l *Label) isLabelOwner(a web.Auth) (bool, error) {
|
|||
}
|
||||
|
||||
// Helper method to check if a user can see a specific label
|
||||
func (l *Label) hasAccessToLabel(a web.Auth) (bool, error) {
|
||||
func (l *Label) hasAccessToLabel(a web.Auth) (has bool, maxRight int, err error) {
|
||||
|
||||
// TODO: add an extra check for link share handling
|
||||
|
||||
// Get all tasks
|
||||
taskIDs, err := getUserTaskIDs(&user.User{ID: a.GetID()})
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
// Get all labels associated with these tasks
|
||||
var labels []*Label
|
||||
has, err := x.Table("labels").
|
||||
Select("labels.*").
|
||||
ll := &LabelTask{}
|
||||
has, err = x.Table("labels").
|
||||
Select("label_task.*").
|
||||
Join("LEFT", "label_task", "label_task.label_id = labels.id").
|
||||
Where("label_task.label_id is not null OR labels.created_by_id = ?", a.GetID()).
|
||||
Or(builder.In("label_task.task_id", taskIDs)).
|
||||
And("labels.id = ?", l.ID).
|
||||
Exist(&labels)
|
||||
return has, err
|
||||
Exist(ll)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Since the right depends on the task the label is associated with, we need to check that too.
|
||||
if ll.TaskID > 0 {
|
||||
t := &Task{ID: ll.TaskID}
|
||||
_, maxRight, err = t.CanRead(a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ func (lt *LabelTask) Create(a web.Auth) (err error) {
|
|||
func (lt *LabelTask) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
// Check if the user has the right to see the task
|
||||
task := Task{ID: lt.TaskID}
|
||||
canRead, err := task.CanRead(a)
|
||||
canRead, _, err := task.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ func (t *Task) updateTaskLabels(creator web.Auth, labels []*Label) (err error) {
|
|||
}
|
||||
|
||||
// Check if the user has the rights to see the label he is about to add
|
||||
hasAccessToLabel, err := label.hasAccessToLabel(creator)
|
||||
hasAccessToLabel, _, err := label.hasAccessToLabel(creator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func (lt *LabelTask) CanCreate(a web.Auth) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
hasAccessTolabel, err := label.hasAccessToLabel(a)
|
||||
hasAccessTolabel, _, err := label.hasAccessToLabel(a)
|
||||
if err != nil || !hasAccessTolabel { // If the user doesn't have access to the label, we can error out here
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ func TestLabel_ReadOne(t *testing.T) {
|
|||
Rights: tt.fields.Rights,
|
||||
}
|
||||
|
||||
allowed, _ := l.CanRead(tt.auth)
|
||||
allowed, _, _ := l.CanRead(tt.auth)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("Label.CanRead() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ func (share *LinkSharing) ReadOne() (err error) {
|
|||
// @Router /lists/{list}/shares [get]
|
||||
func (share *LinkSharing) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
|
||||
list := &List{ID: share.ListID}
|
||||
can, err := list.CanRead(a)
|
||||
can, _, err := list.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -19,15 +19,15 @@ package models
|
|||
import "code.vikunja.io/web"
|
||||
|
||||
// CanRead implements the read right check for a link share
|
||||
func (share *LinkSharing) CanRead(a web.Auth) (bool, error) {
|
||||
func (share *LinkSharing) CanRead(a web.Auth) (bool, int, error) {
|
||||
// Don't allow creating link shares if the user itself authenticated with a link share
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
l, err := GetListByShareHash(share.Hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, 0, err
|
||||
}
|
||||
return l.CanRead(a)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ type ListDuplicate struct {
|
|||
func (ld *ListDuplicate) CanCreate(a web.Auth) (canCreate bool, err error) {
|
||||
// List Exists + user has read access to list
|
||||
ld.List = &List{ID: ld.ListID}
|
||||
canRead, err := ld.List.CanRead(a)
|
||||
canRead, _, err := ld.List.CanRead(a)
|
||||
if err != nil || !canRead {
|
||||
return canRead, err
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (l *List) CanWrite(a web.Auth) (bool, error) {
|
|||
return canWrite, errIsArchived
|
||||
}
|
||||
|
||||
canWrite, err = originalList.checkRight(a, RightWrite, RightAdmin)
|
||||
canWrite, _, err = originalList.checkRight(a, RightWrite, RightAdmin)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -62,21 +62,21 @@ func (l *List) CanWrite(a web.Auth) (bool, error) {
|
|||
}
|
||||
|
||||
// CanRead checks if a user has read access to a list
|
||||
func (l *List) CanRead(a web.Auth) (bool, error) {
|
||||
func (l *List) CanRead(a web.Auth) (bool, int, error) {
|
||||
// Check if the user is either owner or can read
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
return false, err
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
return l.ID == shareAuth.ListID &&
|
||||
(shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), nil
|
||||
(shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), int(shareAuth.Right), nil
|
||||
}
|
||||
|
||||
if l.isOwner(&user.User{ID: a.GetID()}) {
|
||||
return true, nil
|
||||
return true, int(RightAdmin), nil
|
||||
}
|
||||
return l.checkRight(a, RightRead, RightWrite, RightAdmin)
|
||||
}
|
||||
|
@ -123,7 +123,8 @@ func (l *List) IsAdmin(a web.Auth) (bool, error) {
|
|||
if originalList.isOwner(&user.User{ID: a.GetID()}) {
|
||||
return true, nil
|
||||
}
|
||||
return originalList.checkRight(a, RightAdmin)
|
||||
is, _, err := originalList.checkRight(a, RightAdmin)
|
||||
return is, err
|
||||
}
|
||||
|
||||
// Little helper function to check if a user is list owner
|
||||
|
@ -132,7 +133,7 @@ func (l *List) isOwner(u *user.User) bool {
|
|||
}
|
||||
|
||||
// Checks n different rights for any given user
|
||||
func (l *List) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
||||
func (l *List) checkRight(a web.Auth, rights ...Right) (bool, int, error) {
|
||||
|
||||
/*
|
||||
The following loop creates an sql condition like this one:
|
||||
|
@ -174,7 +175,17 @@ func (l *List) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
|||
// 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()})
|
||||
|
||||
exists, err := x.Select("l.*").
|
||||
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.
|
||||
Table("list").
|
||||
Alias("l").
|
||||
// User stuff
|
||||
|
@ -193,6 +204,21 @@ func (l *List) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
|||
),
|
||||
builder.Eq{"l.id": l.ID},
|
||||
)).
|
||||
Exist(&List{})
|
||||
return exists, err
|
||||
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
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ func (tl *TeamList) Delete() (err error) {
|
|||
func (tl *TeamList) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
|
||||
// Check if the user can read the namespace
|
||||
l := &List{ID: tl.ListID}
|
||||
canRead, err := l.CanRead(a)
|
||||
canRead, _, err := l.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ func (lu *ListUser) Delete() (err error) {
|
|||
func (lu *ListUser) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
// Check if the user has access to the list
|
||||
l := &List{ID: lu.ListID}
|
||||
canRead, err := l.CanRead(a)
|
||||
canRead, _, err := l.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -23,16 +23,18 @@ import (
|
|||
|
||||
// CanWrite checks if a user has write access to a namespace
|
||||
func (n *Namespace) CanWrite(a web.Auth) (bool, error) {
|
||||
return n.checkRight(a, RightWrite, RightAdmin)
|
||||
can, _, err := n.checkRight(a, RightWrite, RightAdmin)
|
||||
return can, err
|
||||
}
|
||||
|
||||
// IsAdmin returns true or false if the user is admin on that namespace or not
|
||||
func (n *Namespace) IsAdmin(a web.Auth) (bool, error) {
|
||||
return n.checkRight(a, RightAdmin)
|
||||
is, _, err := n.checkRight(a, RightAdmin)
|
||||
return is, err
|
||||
}
|
||||
|
||||
// CanRead checks if a user has read access to that namespace
|
||||
func (n *Namespace) CanRead(a web.Auth) (bool, error) {
|
||||
func (n *Namespace) CanRead(a web.Auth) (bool, int, error) {
|
||||
return n.checkRight(a, RightRead, RightWrite, RightAdmin)
|
||||
}
|
||||
|
||||
|
@ -56,22 +58,22 @@ func (n *Namespace) CanCreate(a web.Auth) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Namespace) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
||||
func (n *Namespace) checkRight(a web.Auth, rights ...Right) (bool, int, error) {
|
||||
|
||||
// If the auth is a link share, don't do anything
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// Get the namespace and check the right
|
||||
nn := &Namespace{ID: n.ID}
|
||||
err := nn.GetSimpleByID()
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
if a.GetID() == n.OwnerID {
|
||||
return true, nil
|
||||
return true, int(RightAdmin), nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -104,7 +106,14 @@ func (n *Namespace) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
|||
))
|
||||
}
|
||||
|
||||
exists, err := x.Select("namespaces.*").
|
||||
type allRights struct {
|
||||
UserNamespace NamespaceUser `xorm:"extends"`
|
||||
TeamNamespace TeamNamespace `xorm:"extends"`
|
||||
}
|
||||
|
||||
var maxRights = 0
|
||||
r := &allRights{}
|
||||
exists, err := x.Select("*").
|
||||
Table("namespaces").
|
||||
// User stuff
|
||||
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
|
||||
|
@ -118,6 +127,15 @@ func (n *Namespace) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
|||
),
|
||||
builder.Eq{"namespaces.id": n.ID},
|
||||
)).
|
||||
Exist(&List{})
|
||||
return exists, err
|
||||
Exist(r)
|
||||
|
||||
// Figure out the max right and return it
|
||||
if int(r.UserNamespace.Right) > maxRights {
|
||||
maxRights = int(r.UserNamespace.Right)
|
||||
}
|
||||
if int(r.TeamNamespace.Right) > maxRights {
|
||||
maxRights = int(r.TeamNamespace.Right)
|
||||
}
|
||||
|
||||
return exists, maxRights, err
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ func (tn *TeamNamespace) Delete() (err error) {
|
|||
func (tn *TeamNamespace) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
// Check if the user can read the namespace
|
||||
n := Namespace{ID: tn.NamespaceID}
|
||||
canRead, err := n.CanRead(a)
|
||||
canRead, _, err := n.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestNamespace_Create(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// check if it really exists
|
||||
allowed, err = dummynamespace.CanRead(doer)
|
||||
allowed, _, err = dummynamespace.CanRead(doer)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, allowed)
|
||||
err = dummynamespace.ReadOne()
|
||||
|
@ -78,7 +78,7 @@ func TestNamespace_Create(t *testing.T) {
|
|||
// Check if it was updated
|
||||
assert.Equal(t, "Dolor sit amet.", dummynamespace.Description)
|
||||
// Get it and check it again
|
||||
allowed, err = dummynamespace.CanRead(doer)
|
||||
allowed, _, err = dummynamespace.CanRead(doer)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, allowed)
|
||||
err = dummynamespace.ReadOne()
|
||||
|
@ -116,7 +116,7 @@ func TestNamespace_Create(t *testing.T) {
|
|||
assert.True(t, IsErrNamespaceDoesNotExist(err))
|
||||
|
||||
// Check if it was successfully deleted
|
||||
allowed, err = dummynamespace.CanRead(doer)
|
||||
allowed, _, err = dummynamespace.CanRead(doer)
|
||||
assert.False(t, allowed)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNamespaceDoesNotExist(err))
|
||||
|
|
|
@ -160,7 +160,7 @@ func (nu *NamespaceUser) Delete() (err error) {
|
|||
func (nu *NamespaceUser) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
// Check if the user has access to the namespace
|
||||
l := Namespace{ID: nu.NamespaceID}
|
||||
canRead, err := l.CanRead(a)
|
||||
canRead, _, err := l.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ func (t *Task) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
canRead, err := list.CanRead(newAssignee)
|
||||
canRead, _, err := list.CanRead(newAssignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ func (la *TaskAssginee) ReadAll(a web.Auth, search string, page int, perPage int
|
|||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
can, err := task.CanRead(a)
|
||||
can, _, err := task.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package models
|
|||
import "code.vikunja.io/web"
|
||||
|
||||
// CanRead checks if the user can see an attachment
|
||||
func (ta *TaskAttachment) CanRead(a web.Auth) (bool, error) {
|
||||
func (ta *TaskAttachment) CanRead(a web.Auth) (bool, int, error) {
|
||||
t := &Task{ID: ta.TaskID}
|
||||
return t.CanRead(a)
|
||||
}
|
||||
|
|
|
@ -165,14 +165,14 @@ func TestTaskAttachment_Rights(t *testing.T) {
|
|||
t.Run("Allowed", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
ta := &TaskAttachment{TaskID: 1}
|
||||
can, err := ta.CanRead(u)
|
||||
can, _, err := ta.CanRead(u)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, can)
|
||||
})
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
ta := &TaskAttachment{TaskID: 14}
|
||||
can, err := ta.CanRead(u)
|
||||
can, _, err := ta.CanRead(u)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, can)
|
||||
})
|
||||
|
|
|
@ -166,7 +166,7 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
|
|||
} else {
|
||||
// Check the list exists and the user has acess on it
|
||||
list := &List{ID: tf.ListID}
|
||||
canRead, err := list.CanRead(a)
|
||||
canRead, _, err := list.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ package models
|
|||
import "code.vikunja.io/web"
|
||||
|
||||
// CanRead checks if a user can read a comment
|
||||
func (tc *TaskComment) CanRead(a web.Auth) (bool, error) {
|
||||
func (tc *TaskComment) CanRead(a web.Auth) (bool, int, error) {
|
||||
t := Task{ID: tc.TaskID}
|
||||
return t.CanRead(a)
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ func (tc *TaskComment) ReadOne() (err error) {
|
|||
func (tc *TaskComment) ReadAll(auth web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
|
||||
// Check if the user has access to the task
|
||||
canRead, err := tc.CanRead(auth)
|
||||
canRead, _, err := tc.CanRead(auth)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func (rel *TaskRelation) CanCreate(a web.Auth) (bool, error) {
|
|||
|
||||
// We explicitly don't check if the two tasks are on the same list.
|
||||
otherTask := &Task{ID: rel.OtherTaskID}
|
||||
has, err = otherTask.CanRead(a)
|
||||
has, _, err = otherTask.CanRead(a)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func (t *Task) CanCreate(a web.Auth) (bool, error) {
|
|||
}
|
||||
|
||||
// CanRead determines if a user can read a task
|
||||
func (t *Task) CanRead(a web.Auth) (canRead bool, err error) {
|
||||
func (t *Task) CanRead(a web.Auth) (canRead bool, maxRight int, err error) {
|
||||
//return t.canDoTask(a)
|
||||
// Get the task, error out if it doesn't exist
|
||||
*t, err = GetTaskByIDSimple(t.ID)
|
||||
|
|
|
@ -60,9 +60,17 @@ func (t *Team) IsAdmin(a web.Auth) (bool, error) {
|
|||
}
|
||||
|
||||
// CanRead returns true if the user has read access to the team
|
||||
func (t *Team) CanRead(a web.Auth) (bool, error) {
|
||||
func (t *Team) CanRead(a web.Auth) (bool, int, error) {
|
||||
// Check if the user is in the team
|
||||
return x.Where("team_id = ?", t.ID).
|
||||
tm := &TeamMember{}
|
||||
can, err := x.Where("team_id = ?", t.ID).
|
||||
And("user_id = ?", a.GetID()).
|
||||
Get(&TeamMember{})
|
||||
Get(tm)
|
||||
|
||||
maxRights := 0
|
||||
if tm.Admin {
|
||||
maxRights = int(RightAdmin)
|
||||
}
|
||||
|
||||
return can, maxRights, err
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ func TestTeam_CanDoSomething(t *testing.T) {
|
|||
if got, _ := tm.CanUpdate(tt.args.a); got != tt.want["CanUpdate"] {
|
||||
t.Errorf("Team.CanUpdate() = %v, want %v", got, tt.want["CanUpdate"])
|
||||
}
|
||||
if got, _ := tm.CanRead(tt.args.a); got != tt.want["CanRead"] {
|
||||
if got, _, _ := tm.CanRead(tt.args.a); got != tt.want["CanRead"] {
|
||||
t.Errorf("Team.CanRead() = %v, want %v", got, tt.want["CanRead"])
|
||||
}
|
||||
if got, _ := tm.IsAdmin(tt.args.a); got != tt.want["IsAdmin"] {
|
||||
|
|
|
@ -191,7 +191,7 @@ func GetListBackground(c echo.Context) error {
|
|||
|
||||
// Check if a background for this list exists + Rights
|
||||
list := &models.List{ID: listID}
|
||||
can, err := list.CanRead(auth)
|
||||
can, _, err := list.CanRead(auth)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ func getNamespace(c echo.Context) (namespace *models.Namespace, err error) {
|
|||
return
|
||||
}
|
||||
namespace = &models.Namespace{ID: namespaceID}
|
||||
canRead, err := namespace.CanRead(user)
|
||||
canRead, _, err := namespace.CanRead(user)
|
||||
if err != nil {
|
||||
return namespace, err
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ func GetTaskAttachment(c echo.Context) error {
|
|||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
can, err := taskAttachment.CanRead(auth)
|
||||
can, _, err := taskAttachment.CanRead(auth)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ func ListUsersForList(c echo.Context) error {
|
|||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
canRead, err := list.CanRead(auth)
|
||||
canRead, _, err := list.CanRead(auth)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
|
|
@ -383,7 +383,7 @@ func (vlra *VikunjaListResourceAdapter) GetModTime() time.Time {
|
|||
}
|
||||
|
||||
func (vcls *VikunjaCaldavListStorage) getListRessource(isCollection bool) (rr VikunjaListResourceAdapter, err error) {
|
||||
can, err := vcls.list.CanRead(vcls.user)
|
||||
can, _, err := vcls.list.CanRead(vcls.user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
// @description Every endpoint capable of pagination will return two headers:
|
||||
// @description * `x-pagination-total-pages`: The total number of available pages for this request
|
||||
// @description * `x-pagination-result-count`: The number of items returned for this request.
|
||||
// @description # Rights
|
||||
// @description All endpoints which return a single item (list, task, namespace, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read & Write` and `2` is `Admin`.
|
||||
// @description This can be used to show or hide ui elements based on the rights the user has.
|
||||
// @description # Authorization
|
||||
// @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.
|
||||
// @description
|
||||
|
|
|
@ -7436,7 +7436,7 @@ var SwaggerInfo = swaggerInfo{
|
|||
BasePath: "/api/v1",
|
||||
Schemes: []string{},
|
||||
Title: "Vikunja API",
|
||||
Description: "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n<!-- ReDoc-Inject: <security-definitions> -->",
|
||||
Description: "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Rights\nAll endpoints which return a single item (list, task, namespace, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read & Write` and `2` is `Admin`.\nThis can be used to show or hide ui elements based on the rights the user has.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n<!-- ReDoc-Inject: <security-definitions> -->",
|
||||
}
|
||||
|
||||
type s struct{}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer \u003cjwt-token\u003e`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e",
|
||||
"description": "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Rights\nAll endpoints which return a single item (list, task, namespace, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read \u0026 Write` and `2` is `Admin`.\nThis can be used to show or hide ui elements based on the rights the user has.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer \u003cjwt-token\u003e`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e",
|
||||
"title": "Vikunja API",
|
||||
"contact": {
|
||||
"name": "General Vikunja contact",
|
||||
|
|
|
@ -987,6 +987,9 @@ info:
|
|||
Every endpoint capable of pagination will return two headers:
|
||||
* `x-pagination-total-pages`: The total number of available pages for this request
|
||||
* `x-pagination-result-count`: The number of items returned for this request.
|
||||
# Rights
|
||||
All endpoints which return a single item (list, task, namespace, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read & Write` and `2` is `Admin`.
|
||||
This can be used to show or hide ui elements based on the rights the user has.
|
||||
# Authorization
|
||||
**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.
|
||||
|
||||
|
|
Loading…
Reference in New Issue