Fix rights checks (#70)

This commit is contained in:
konrad 2019-04-01 19:48:48 +00:00 committed by Gitea
parent 19faee0102
commit 87873e53c5
17 changed files with 92 additions and 115 deletions

View File

@ -75,17 +75,18 @@ Sorry for some of them being in German, I'll tranlate them at some point.
* [x] Panic wenn mailer nicht erreichbar -> Als workaround mailer deaktivierbar machen, bzw keine mails verschicken * [x] Panic wenn mailer nicht erreichbar -> Als workaround mailer deaktivierbar machen, bzw keine mails verschicken
* [x] "unexpected EOF" * [x] "unexpected EOF"
* [x] Beim Login & Password reset gibt die API zurück dass der Nutzer nicht existiert * [x] Beim Login & Password reset gibt die API zurück dass der Nutzer nicht existiert
* [ ] Re-check rights checks to see if all information which is compared against is properly read from the db and not only based on user input * [x] Re-check rights checks to see if all information which is compared against is properly read from the db and not only based on user input
* [ ] Lists * [x] Lists
* [ ] List users * [x] List users
* [ ] List Teams * [x] List Teams
* [ ] Labels * [x] Labels
* [ ] Tasks * [x] Tasks
* [ ] Namespaces * [x] Namespaces
* [ ] Namespace users * [x] Namespace users
* [ ] Namespace teams * [x] Namespace teams
* [ ] Teams * [x] Teams
* [ ] Team member handling * [x] Team member handling
* [x] Also check `ReadOne()` for unnessecary database operations since the inital query is already done in `CanRead()`
* [x] Add a `User.AfterLoad()` which obfuscates the email address * [x] Add a `User.AfterLoad()` which obfuscates the email address
* [ ] Sometimes `done` from a task is not updated (returns `done: false` but `done:true` is being sent to the server) * [ ] Sometimes `done` from a task is not updated (returns `done: false` but `done:true` is being sent to the server)

View File

@ -109,11 +109,7 @@ func (lt *LabelTask) ReadAll(search string, a web.Auth, page int) (labels interf
} }
// Check if the user has the right to see the task // Check if the user has the right to see the task
task, err := GetListTaskByID(lt.TaskID) task := ListTask{ID: lt.TaskID}
if err != nil {
return nil, err
}
canRead, err := task.CanRead(a) canRead, err := task.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -113,11 +113,6 @@ func (l *List) ReadAll(search string, a web.Auth, page int) (interface{}, error)
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id} [get] // @Router /lists/{id} [get]
func (l *List) ReadOne() (err error) { func (l *List) ReadOne() (err error) {
err = l.GetSimpleByID()
if err != nil {
return err
}
// Get list tasks // Get list tasks
l.Tasks, err = GetTasksByListID(l.ID) l.Tasks, err = GetTasksByListID(l.ID)
if err != nil { if err != nil {

View File

@ -46,6 +46,9 @@ func TestList_Create(t *testing.T) {
// Get the list // Get the list
newdummy := List{ID: dummylist.ID} newdummy := List{ID: dummylist.ID}
canRead, err := newdummy.CanRead(&doer)
assert.NoError(t, err)
assert.True(t, canRead)
err = newdummy.ReadOne() err = newdummy.ReadOne()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, dummylist.Title, newdummy.Title) assert.Equal(t, dummylist.Title, newdummy.Title)

View File

@ -48,6 +48,11 @@ func CreateOrUpdateList(list *List) (err error) {
return return
} }
err = list.GetSimpleByID()
if err != nil {
return
}
err = list.ReadOne() err = list.ReadOne()
return return

View File

@ -40,13 +40,15 @@ func (t *ListTask) CanCreate(a web.Auth) (bool, error) {
} }
// CanRead determines if a user can read a task // CanRead determines if a user can read a task
func (t *ListTask) CanRead(a web.Auth) (bool, error) { func (t *ListTask) CanRead(a web.Auth) (canRead bool, err error) {
// Get the task, error out if it doesn't exist
*t, err = getTaskByIDSimple(t.ID)
if err != nil {
return
}
// A user can read a task if it has access to the list // A user can read a task if it has access to the list
list := &List{ID: t.ListID} list := &List{ID: t.ListID}
err := list.GetSimpleByID()
if err != nil {
return false, err
}
return list.CanRead(a) return list.CanRead(a)
} }

View File

@ -40,9 +40,6 @@ func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, e
// Check if the user has access to the list // Check if the user has access to the list
l := &List{ID: lu.ListID} l := &List{ID: lu.ListID}
if err := l.GetSimpleByID(); err != nil {
return nil, err
}
canRead, err := l.CanRead(u) canRead, err := l.CanRead(u)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -18,6 +18,7 @@ package models
import ( import (
"code.vikunja.io/web" "code.vikunja.io/web"
"github.com/imdario/mergo"
"time" "time"
) )
@ -69,13 +70,19 @@ func (n *Namespace) GetSimpleByID() (err error) {
return return
} }
exists, err := x.Get(n) namespaceFromDB := &Namespace{}
exists, err := x.Where("id = ?", n.ID).Get(namespaceFromDB)
if err != nil { if err != nil {
return return
} }
if !exists { if !exists {
return ErrNamespaceDoesNotExist{ID: n.ID} return ErrNamespaceDoesNotExist{ID: n.ID}
} }
// We don't want to override the provided user struct because this would break updating, so we have to merge it
if err := mergo.Merge(namespaceFromDB, n, mergo.WithOverride); err != nil {
return err
}
*n = *namespaceFromDB
return return
} }
@ -90,10 +97,6 @@ func GetNamespaceByID(id int64) (namespace Namespace, err error) {
// Get the namespace Owner // Get the namespace Owner
namespace.Owner, err = GetUserByID(namespace.OwnerID) namespace.Owner, err = GetUserByID(namespace.OwnerID)
if err != nil {
return
}
return return
} }
@ -110,7 +113,8 @@ func GetNamespaceByID(id int64) (namespace Namespace, err error) {
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{id} [get] // @Router /namespaces/{id} [get]
func (n *Namespace) ReadOne() (err error) { func (n *Namespace) ReadOne() (err error) {
*n, err = GetNamespaceByID(n.ID) // Get the namespace Owner
n.Owner, err = GetUserByID(n.OwnerID)
return return
} }

View File

@ -23,49 +23,17 @@ import (
// CanWrite checks if a user has write access to a namespace // CanWrite checks if a user has write access to a namespace
func (n *Namespace) CanWrite(a web.Auth) (bool, error) { func (n *Namespace) CanWrite(a web.Auth) (bool, error) {
return n.checkRight(a, RightWrite, RightAdmin)
// Get the namespace and check the right
originalNamespace := &Namespace{ID: n.ID}
err := originalNamespace.GetSimpleByID()
if err != nil {
return false, err
}
u := getUserForRights(a)
if originalNamespace.isOwner(u) {
return true, nil
}
return originalNamespace.checkRight(u, RightWrite, RightAdmin)
} }
// IsAdmin returns true or false if the user is admin on that namespace or not // IsAdmin returns true or false if the user is admin on that namespace or not
func (n *Namespace) IsAdmin(a web.Auth) (bool, error) { func (n *Namespace) IsAdmin(a web.Auth) (bool, error) {
originalNamespace := &Namespace{ID: n.ID} return n.checkRight(a, RightAdmin)
err := originalNamespace.GetSimpleByID()
if err != nil {
return false, err
}
u := getUserForRights(a)
if originalNamespace.isOwner(u) {
return true, nil
}
return originalNamespace.checkRight(u, RightAdmin)
} }
// CanRead checks if a user has read access to that namespace // 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, error) {
originalNamespace := &Namespace{ID: n.ID} return n.checkRight(a, RightRead, RightWrite, RightAdmin)
err := originalNamespace.GetSimpleByID()
if err != nil {
return false, err
}
u := getUserForRights(a)
if originalNamespace.isOwner(u) {
return true, nil
}
return n.checkRight(u, RightRead, RightWrite, RightAdmin)
} }
// CanUpdate checks if the user can update the namespace // CanUpdate checks if the user can update the namespace
@ -84,12 +52,18 @@ func (n *Namespace) CanCreate(a web.Auth) (bool, error) {
return true, nil return true, nil
} }
// Small helper function to check if a user owns the namespace func (n *Namespace) checkRight(a web.Auth, rights ...Right) (bool, error) {
func (n *Namespace) isOwner(user *User) bool {
return n.OwnerID == user.ID
}
func (n *Namespace) checkRight(user *User, rights ...Right) (bool, error) { // Get the namespace and check the right
err := n.GetSimpleByID()
if err != nil {
return false, err
}
user := getUserForRights(a)
if user.ID == n.OwnerID {
return true, nil
}
/* /*
The following loop creates an sql condition like this one: The following loop creates an sql condition like this one:

View File

@ -43,12 +43,12 @@ func TestNamespace_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// check if it really exists // check if it really exists
allowed, _ = dummynamespace.CanRead(&doer) allowed, err = dummynamespace.CanRead(&doer)
assert.True(t, allowed)
newOne := Namespace{ID: dummynamespace.ID}
err = newOne.ReadOne()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, newOne.Name, "Test") assert.True(t, allowed)
err = dummynamespace.ReadOne()
assert.NoError(t, err)
assert.Equal(t, dummynamespace.Name, "Test")
// Try creating one without a name // Try creating one without a name
n2 := Namespace{} n2 := Namespace{}
@ -64,12 +64,23 @@ func TestNamespace_Create(t *testing.T) {
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrUserDoesNotExist(err))
// Update it // Update it
allowed, _ = dummynamespace.CanUpdate(&doer) allowed, err = dummynamespace.CanUpdate(&doer)
assert.NoError(t, err)
assert.True(t, allowed) assert.True(t, allowed)
dummynamespace.Description = "Dolor sit amet." dummynamespace.Description = "Dolor sit amet."
err = dummynamespace.Update() err = dummynamespace.Update()
assert.NoError(t, err) assert.NoError(t, err)
// Check if it was updated
assert.Equal(t, "Dolor sit amet.", dummynamespace.Description)
// Get it and check it again
allowed, err = dummynamespace.CanRead(&doer)
assert.NoError(t, err)
assert.True(t, allowed)
err = dummynamespace.ReadOne()
assert.NoError(t, err)
assert.Equal(t, "Dolor sit amet.", dummynamespace.Description)
// Try updating one with a nonexistant owner // Try updating one with a nonexistant owner
dummynamespace.Owner.ID = 94829838572 dummynamespace.Owner.ID = 94829838572
err = dummynamespace.Update() err = dummynamespace.Update()
@ -89,7 +100,8 @@ func TestNamespace_Create(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
// Delete it // Delete it
allowed, _ = dummynamespace.CanDelete(&doer) allowed, err = dummynamespace.CanDelete(&doer)
assert.NoError(t, err)
assert.True(t, allowed) assert.True(t, allowed)
err = dummynamespace.Delete() err = dummynamespace.Delete()
assert.NoError(t, err) assert.NoError(t, err)
@ -100,7 +112,8 @@ func TestNamespace_Create(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
// Check if it was successfully deleted // Check if it was successfully deleted
err = dummynamespace.ReadOne() allowed, err = dummynamespace.CanRead(&doer)
assert.False(t, allowed)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))

View File

@ -39,10 +39,7 @@ func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface
} }
// Check if the user has access to the namespace // Check if the user has access to the namespace
l, err := GetNamespaceByID(nu.NamespaceID) l := Namespace{ID: nu.NamespaceID}
if err != nil {
return nil, err
}
canRead, err := l.CanRead(u) canRead, err := l.CanRead(u)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -40,9 +40,6 @@ func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, e
// Check if the user can read the namespace // Check if the user can read the namespace
l := &List{ID: tl.ListID} l := &List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil {
return nil, err
}
canRead, err := l.CanRead(u) canRead, err := l.CanRead(u)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -39,10 +39,7 @@ func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface
} }
// Check if the user can read the namespace // Check if the user can read the namespace
n, err := GetNamespaceByID(tn.NamespaceID) n := Namespace{ID: tn.NamespaceID}
if err != nil {
return nil, err
}
canRead, err := n.CanRead(user) canRead, err := n.CanRead(user)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -34,12 +34,6 @@ import (
// @Router /teams/{id} [delete] // @Router /teams/{id} [delete]
func (t *Team) Delete() (err error) { func (t *Team) Delete() (err error) {
// Check if the team exists
_, err = GetTeamByID(t.ID)
if err != nil {
return
}
// Delete the team // Delete the team
_, err = x.ID(t.ID).Delete(&Team{}) _, err = x.ID(t.ID).Delete(&Team{})
if err != nil { if err != nil {

View File

@ -28,13 +28,7 @@ func (t *Team) CanCreate(a web.Auth) (bool, error) {
// CanUpdate checks if the user can update a team // CanUpdate checks if the user can update a team
func (t *Team) CanUpdate(a web.Auth) (bool, error) { func (t *Team) CanUpdate(a web.Auth) (bool, error) {
u := getUserForRights(a) return t.IsAdmin(a)
// Check if the current user is in the team and has admin rights in it
return x.Where("team_id = ?", t.ID).
And("user_id = ?", u.ID).
And("admin = ?", true).
Get(&TeamMember{})
} }
// CanDelete checks if a user can delete a team // CanDelete checks if a user can delete a team
@ -46,6 +40,12 @@ func (t *Team) CanDelete(a web.Auth) (bool, error) {
func (t *Team) IsAdmin(a web.Auth) (bool, error) { func (t *Team) IsAdmin(a web.Auth) (bool, error) {
u := getUserForRights(a) u := getUserForRights(a)
// Check if the team exists to be able to return a proper error message if not
_, err := GetTeamByID(t.ID)
if err != nil {
return false, err
}
return x.Where("team_id = ?", t.ID). return x.Where("team_id = ?", t.ID).
And("user_id = ?", u.ID). And("user_id = ?", u.ID).
And("admin = ?", true). And("admin = ?", true).

View File

@ -80,13 +80,15 @@ func TestTeam_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Delete it // Delete it
allowed, _ = dummyteam.CanDelete(&doer) allowed, err = dummyteam.CanDelete(&doer)
assert.NoError(t, err)
assert.True(t, allowed) assert.True(t, allowed)
err = dummyteam.Delete() err = dummyteam.Delete()
assert.NoError(t, err) assert.NoError(t, err)
// Try deleting a (now) nonexistant team // Try deleting a (now) nonexistant team
err = dummyteam.Delete() allowed, err = dummyteam.CanDelete(&doer)
assert.False(t, allowed)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err)) assert.True(t, IsErrTeamDoesNotExist(err))

View File

@ -80,12 +80,6 @@ func getNamespace(c echo.Context) (namespace models.Namespace, err error) {
return return
} }
// Get the namespace
namespace, err = models.GetNamespaceByID(namespaceID)
if err != nil {
return
}
// Check if the user has acces to that namespace // Check if the user has acces to that namespace
user, err := models.GetCurrentUser(c) user, err := models.GetCurrentUser(c)
if err != nil { if err != nil {
@ -99,5 +93,11 @@ func getNamespace(c echo.Context) (namespace models.Namespace, err error) {
return return
} }
// Get the namespace
namespace, err = models.GetNamespaceByID(namespaceID)
if err != nil {
return
}
return return
} }