mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-04-24 14:15:18 +00:00
Authoritative size now comes from the reader instead of the caller's claim in CreateWithMimeAndSession. The migration import path accepts attacker-controlled metadata (GHSA-qh78-rvg3-cv54), so trusting realsize for the limit check allowed oversized uploads to be accepted and stored. measureReaderSize leaves the reader seeked to 0 so the measured value matches the bytes storage backends will actually write.
299 lines
7.9 KiB
Go
299 lines
7.9 KiB
Go
// Vikunja is a to-do list application to facilitate your life.
|
|
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package models
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
|
|
"code.vikunja.io/api/pkg/db"
|
|
"code.vikunja.io/api/pkg/files"
|
|
"code.vikunja.io/api/pkg/user"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTaskAttachment_ReadOne(t *testing.T) {
|
|
u := &user.User{ID: 1}
|
|
|
|
t.Run("Normal File", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
files.InitTestFileFixtures(t)
|
|
ta := &TaskAttachment{
|
|
ID: 1,
|
|
}
|
|
err := ta.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, ta.File)
|
|
assert.True(t, ta.File.ID == ta.FileID && ta.FileID != 0)
|
|
|
|
// Load the actual attachment file and check its content
|
|
err = ta.File.LoadFileByID()
|
|
require.NoError(t, err)
|
|
|
|
// Validate the file exists at the expected storage path
|
|
stat, err := files.FileStat(ta.File)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, stat)
|
|
|
|
content := make([]byte, 9)
|
|
read, err := ta.File.File.Read(content)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 9, read)
|
|
assert.Equal(t, []byte("testfile1"), content)
|
|
})
|
|
t.Run("Nonexisting Attachment", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
files.InitTestFileFixtures(t)
|
|
ta := &TaskAttachment{
|
|
ID: 9999,
|
|
}
|
|
err := ta.ReadOne(s, u)
|
|
require.Error(t, err)
|
|
assert.True(t, IsErrTaskAttachmentDoesNotExist(err))
|
|
})
|
|
t.Run("Existing Attachment, Nonexisting File", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
files.InitTestFileFixtures(t)
|
|
ta := &TaskAttachment{
|
|
ID: 2,
|
|
}
|
|
err := ta.ReadOne(s, u)
|
|
require.Error(t, err)
|
|
require.EqualError(t, err, "file 9999 does not exist")
|
|
})
|
|
}
|
|
|
|
func TestTaskAttachment_NewAttachment(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
files.InitTestFileFixtures(t)
|
|
// Assert the file is being stored correctly
|
|
ta := TaskAttachment{
|
|
TaskID: 1,
|
|
}
|
|
testuser := &user.User{ID: 1}
|
|
|
|
content := []byte("testingstuff")
|
|
err := ta.NewAttachment(s, bytes.NewReader(content), "testfile", uint64(len(content)), testuser)
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, 0, ta.FileID)
|
|
_, err = files.FileStat(ta.File)
|
|
require.NoError(t, err)
|
|
assert.False(t, os.IsNotExist(err))
|
|
assert.Equal(t, testuser.ID, ta.CreatedByID)
|
|
|
|
// Commit so that LoadFileMetaByID (which reads via the global engine) can see the data
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
// Check the file was inserted correctly
|
|
ta.File = &files.File{ID: ta.FileID}
|
|
err = ta.File.LoadFileMetaByID()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, testuser.ID, ta.File.CreatedByID)
|
|
assert.Equal(t, "testfile", ta.File.Name)
|
|
assert.Equal(t, uint64(len(content)), ta.File.Size)
|
|
assert.NotEmpty(t, ta.File.Mime, "mime type should be detected and stored")
|
|
|
|
// Extra test for max size test
|
|
}
|
|
|
|
func TestTaskAttachment_ReadAll(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
files.InitTestFileFixtures(t)
|
|
ta := &TaskAttachment{TaskID: 1}
|
|
as, _, _, err := ta.ReadAll(s, &user.User{ID: 1}, "", 0, 50)
|
|
attachments, _ := as.([]*TaskAttachment)
|
|
require.NoError(t, err)
|
|
assert.Len(t, attachments, 3)
|
|
assert.Equal(t, "test", attachments[0].File.Name)
|
|
for _, a := range attachments {
|
|
assert.NotNil(t, a.CreatedBy)
|
|
}
|
|
assert.Equal(t, int64(-2), attachments[2].CreatedByID)
|
|
assert.Equal(t, int64(-2), attachments[2].CreatedBy.ID)
|
|
}
|
|
|
|
func TestTaskAttachment_Delete(t *testing.T) {
|
|
u := &user.User{ID: 1}
|
|
|
|
t.Run("Normal", func(t *testing.T) {
|
|
files.InitTestFileFixtures(t)
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{ID: 1}
|
|
err := ta.Delete(s, u)
|
|
require.NoError(t, err)
|
|
// Check if the file itself was deleted
|
|
_, err = files.FileStat(ta.File) // The new file has the id 2 since it's the second attachment
|
|
require.Error(t, err)
|
|
assert.True(t, os.IsNotExist(err))
|
|
})
|
|
t.Run("Nonexisting", func(t *testing.T) {
|
|
files.InitTestFileFixtures(t)
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{ID: 9999}
|
|
err := ta.Delete(s, u)
|
|
require.Error(t, err)
|
|
assert.True(t, IsErrTaskAttachmentDoesNotExist(err))
|
|
})
|
|
t.Run("Existing attachment, nonexisting file", func(t *testing.T) {
|
|
files.InitTestFileFixtures(t)
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{ID: 2}
|
|
err := ta.Delete(s, u)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestAttachmentPreviewRejectsLargeImages(t *testing.T) {
|
|
// Create a 10000x10000 pixel image (100M pixels, well above the 50M limit)
|
|
// As a PNG this is small on disk but huge when decoded into memory
|
|
img := image.NewNRGBA(image.Rect(0, 0, 10000, 10000))
|
|
var buf bytes.Buffer
|
|
err := png.Encode(&buf, img)
|
|
require.NoError(t, err)
|
|
|
|
attachment := &TaskAttachment{
|
|
ID: 999999,
|
|
File: &files.File{
|
|
File: io.NopCloser(bytes.NewReader(buf.Bytes())),
|
|
},
|
|
}
|
|
|
|
result := attachment.GetPreview(PreviewMedium)
|
|
assert.Nil(t, result, "Preview should be nil for images exceeding max pixel count")
|
|
}
|
|
|
|
func TestTaskAttachment_Permissions(t *testing.T) {
|
|
u := &user.User{ID: 1}
|
|
t.Run("Can Read", func(t *testing.T) {
|
|
t.Run("Allowed", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 1}
|
|
can, _, err := ta.CanRead(s, u)
|
|
require.NoError(t, err)
|
|
assert.True(t, can)
|
|
})
|
|
t.Run("Forbidden", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 14}
|
|
can, _, err := ta.CanRead(s, u)
|
|
require.NoError(t, err)
|
|
assert.False(t, can)
|
|
})
|
|
})
|
|
t.Run("Can Delete", func(t *testing.T) {
|
|
t.Run("Allowed", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 1}
|
|
can, err := ta.CanDelete(s, u)
|
|
require.NoError(t, err)
|
|
assert.True(t, can)
|
|
})
|
|
t.Run("Forbidden, no access", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 14}
|
|
can, err := ta.CanDelete(s, u)
|
|
require.NoError(t, err)
|
|
assert.False(t, can)
|
|
})
|
|
t.Run("Forbidden, shared read only", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 15}
|
|
can, err := ta.CanDelete(s, u)
|
|
require.NoError(t, err)
|
|
assert.False(t, can)
|
|
})
|
|
})
|
|
t.Run("Can Create", func(t *testing.T) {
|
|
t.Run("Allowed", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 1}
|
|
can, err := ta.CanCreate(s, u)
|
|
require.NoError(t, err)
|
|
assert.True(t, can)
|
|
})
|
|
t.Run("Forbidden, no access", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 14}
|
|
can, err := ta.CanCreate(s, u)
|
|
require.NoError(t, err)
|
|
assert.False(t, can)
|
|
})
|
|
t.Run("Forbidden, shared read only", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
ta := &TaskAttachment{TaskID: 15}
|
|
can, err := ta.CanCreate(s, u)
|
|
require.NoError(t, err)
|
|
assert.False(t, can)
|
|
})
|
|
})
|
|
}
|