Enable a list to be moved across namespaces (#1096)

Co-authored-by: Simon Hilchenbach <simon@hilchenba.ch>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1096
Reviewed-by: konrad <k@knt.li>
Co-authored-by: shilch <simon@hilchenba.ch>
Co-committed-by: shilch <simon@hilchenba.ch>
This commit is contained in:
shilch 2022-01-23 12:59:43 +00:00 committed by konrad
parent da318e3db1
commit f7a06e4644
7 changed files with 89 additions and 4 deletions

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"go.testEnvVars": {
"VIKUNJA_SERVICE_ROOTPATH": "${workspaceRoot}"
}
}

View file

@ -11,9 +11,9 @@ menu:
# Mage # Mage
Vikunja uses [Mage](https://magefile.org/) to script common development tasks and even releasing. Vikunja uses [Mage](https://magefile.org/) to script common development tasks and even releasing.
Mage is a pure go solution which allows for greater flexibility and things like better paralelization. Mage is a pure go solution which allows for greater flexibility and things like better parallelization.
This document explains what taks are available and what they do. This document explains what tasks are available and what they do.
{{< table_of_contents >}} {{< table_of_contents >}}

View file

@ -14,6 +14,7 @@
// 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/>.
//go:build mage
// +build mage // +build mage
package main package main
@ -349,7 +350,7 @@ func (Test) Unit() {
mg.Deps(initVars) mg.Deps(initVars)
setApiPackages() setApiPackages()
// We run everything sequentially and not in parallel to prevent issues with real test databases // We run everything sequentially and not in parallel to prevent issues with real test databases
args := append([]string{"test", Goflags[0], "-p", "1", "-timeout", "20m"}, ApiPackages...) args := append([]string{"test", Goflags[0], "-p", "1", "-coverprofile", "cover.out", "-timeout", "20m"}, ApiPackages...)
runAndStreamOutput("go", args...) runAndStreamOutput("go", args...)
} }

View file

@ -630,6 +630,7 @@ func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground
"is_archived", "is_archived",
"identifier", "identifier",
"hex_color", "hex_color",
"namespace_id",
"position", "position",
} }
if list.Description != "" { if list.Description != "" {

View file

@ -116,6 +116,25 @@ func (l *List) CanUpdate(s *xorm.Session, a web.Auth) (canUpdate bool, err error
return false, nil return false, nil
} }
// Get the list
ol, err := GetListSimpleByID(s, l.ID)
if err != nil {
return false, err
}
// Check if we're moving the list into a different namespace.
// If that is the case, we need to verify permissions to do so.
if l.NamespaceID != 0 && l.NamespaceID != ol.NamespaceID {
newNamespace := &Namespace{ID: l.NamespaceID}
can, err := newNamespace.CanWrite(s, a)
if err != nil {
return false, err
}
if !can {
return false, ErrGenericForbidden{}
}
}
fid := getSavedFilterIDFromListID(l.ID) fid := getSavedFilterIDFromListID(l.ID)
if fid > 0 { if fid > 0 {
sf, err := getSavedFilterSimpleByID(s, fid) sf, err := getSavedFilterSimpleByID(s, fid)

View file

@ -163,6 +163,65 @@ func TestList_CreateOrUpdate(t *testing.T) {
assert.True(t, IsErrListIdentifierIsNotUnique(err)) assert.True(t, IsErrListIdentifierIsNotUnique(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("change namespace", func(t *testing.T) {
t.Run("own", func(t *testing.T) {
usr := &user.User{
ID: 6,
Username: "user6",
Email: "user6@example.com",
}
db.LoadAndAssertFixtures(t)
s := db.NewSession()
list := List{
ID: 6,
Title: "Test6",
Description: "Lorem Ipsum",
NamespaceID: 7, // from 6
}
can, err := list.CanUpdate(s, usr)
assert.NoError(t, err)
assert.True(t, can)
err = list.Update(s, usr)
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
db.AssertExists(t, "lists", map[string]interface{}{
"id": list.ID,
"title": list.Title,
"description": list.Description,
"namespace_id": list.NamespaceID,
}, false)
})
// FIXME: The check for whether the namespace is archived is missing in namespace.CanWrite
// t.Run("archived own", func(t *testing.T) {
// db.LoadAndAssertFixtures(t)
// s := db.NewSession()
// list := List{
// ID: 1,
// Title: "Test1",
// Description: "Lorem Ipsum",
// NamespaceID: 16, // from 1
// }
// can, err := list.CanUpdate(s, usr)
// assert.NoError(t, err)
// assert.False(t, can) // namespace is archived and thus not writeable
// _ = s.Close()
// })
t.Run("others", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
list := List{
ID: 1,
Title: "Test1",
Description: "Lorem Ipsum",
NamespaceID: 2, // from 1
}
can, _ := list.CanUpdate(s, usr)
assert.False(t, can) // namespace is not writeable by us
_ = s.Close()
})
})
}) })
} }

View file

@ -9215,5 +9215,5 @@ func (s *s) ReadDoc() string {
} }
func init() { func init() {
swag.Register(swag.Name, &s{}) swag.Register("swagger", &s{})
} }