mirror of
https://github.com/mattermost/mattermost.git
synced 2026-05-28 04:35:04 -04:00
* feat: filewillbedonwloaded hook * feat: error popup * chore: make generated pluginapi * tests * feat: different errors for different download types * feat: allow toast positions * fix: avoid using deprecated i18n function * feat: add plugin API to show toasts * feat: downloadType parameter * tests: updated tests * chore: make check-style * chore: i18n * chore: missing fields in tests * chore: sorted i18n for webapp * chore: run mmjstool * test: fixed webapp tests with new changes * test: missing mocks * fix: ensure one-file attachments (previews) are handler properly as thumbnails * chore: lint * test: added new logic to tests * chore: lint * Add SendToastMessage API and FileWillBeDownloaded hook - Introduced SendToastMessage method for sending toast notifications to users with customizable options. - Added FileWillBeDownloaded hook to handle file download requests, allowing plugins to control access to files. - Updated related types and constants for file download handling. - Enhanced PluginSettings to include HookTimeoutSeconds for better timeout management. * Update webapp/channels/src/components/single_image_view/single_image_view.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: copilot reviews * test: head requests * chore: linted the webapp * tests: fixed path * test: fixed mocked args * allow sending message to a connection directly * fix: hook thread safety * chore: formatting * chore: remove configuration from system console * chore: release version * chore: update signature * chore: update release version * chore: addressed comments * fix: update file rejection handling to use 403 Forbidden status and include rejection reason header * Fix nil pointer panic in runFileWillBeDownloadedHook The atomic.Value in runFileWillBeDownloadedHook can be nil if no plugins implement the FileWillBeDownloaded hook. This causes a panic when trying to assert the nil interface to string. This fix adds a nil check before the type assertion, defaulting to an empty string (which allows the download) when no hooks have run. Fixes: - TestUploadDataMultipart/success panic - TestUploadDataMultipart/resume_success panic * test: move the logout test last * chore: restored accidential deletion * chore: lint * chore: make generated * refactor: move websocket events to new package * chore: go vet * chore: missing mock * chore: revert incorrect fmt * chore: import ordering * chore: npm i18n-extract * chore: update constants.tsx from master * chore: make i18n-extract * revert: conflict merge * fix: add missing isFileRejected prop to SingleImageView tests * fix: mock fetch in SingleImageView tests for async thumbnail check The component now performs an async fetch to check thumbnail availability before rendering. Tests need to mock fetch and use waitFor to handle the async state updates. * refactor: move hook logic to app layer * chore: update version to 11.5 * Scope file download rejection toast to the requesting connection Thread the Connection-Id header through RunFileWillBeDownloadedHook and sendFileDownloadRejectedEvent so the WebSocket event is sent only to the connection that initiated the download, instead of all connections for the user. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
529 lines
19 KiB
Go
529 lines
19 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest"
|
|
)
|
|
|
|
func TestHookFileWillBeDownloaded(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
t.Run("rejected", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
// Allow any logging calls (not verified in this test)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogInfo("Rejecting file download", "file_id", info.Id, "user_id", userID)
|
|
return "Download blocked by security policy"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file first
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, fileInfo)
|
|
|
|
// Get the file info to pass to the hook
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Call the hook through the app method
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
// Verify the file download was rejected
|
|
assert.NotEmpty(t, rejectionReason)
|
|
assert.Contains(t, rejectionReason, "blocked by security policy")
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
// Allow any logging calls (not verified in this test)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogInfo("Allowing file download", "file_id", info.Id, "user_id", userID)
|
|
// Return empty string to allow download
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, fileInfo)
|
|
|
|
// Get the file info
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Call the hook through the app method
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
// Verify the file download was allowed
|
|
assert.Empty(t, rejectionReason)
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("multiple plugins - first rejects", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI1 plugintest.API
|
|
mockAPI1.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
// Allow any logging calls (not verified in this test)
|
|
mockAPI1.On("LogInfo", mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogWarn", mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogWarn", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI1.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
var mockAPI2 plugintest.API
|
|
mockAPI2.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
// This plugin should NOT be called because first one rejects
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
// First plugin - rejects
|
|
`
|
|
package main
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogWarn("First plugin rejecting", "file_id", info.Id)
|
|
return "Rejected by first plugin"
|
|
}
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
// Second plugin - should not be called
|
|
`
|
|
package main
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogInfo("Second plugin should not be called")
|
|
return ""
|
|
}
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API {
|
|
// Alternate between the two mock APIs
|
|
return &mockAPI1
|
|
})
|
|
defer tearDown()
|
|
|
|
// Upload a file
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Call hooks - first one should reject, second should not be called
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
assert.NotEmpty(t, rejectionReason)
|
|
assert.Contains(t, rejectionReason, "Rejected by first plugin")
|
|
|
|
// Only first mock API should have been called
|
|
mockAPI1.AssertExpectations(t)
|
|
// mockAPI2 should not have been called (no expectations set)
|
|
})
|
|
|
|
t.Run("no plugins installed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// No plugins - hook should return empty
|
|
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
// No plugins means no rejection
|
|
assert.Empty(t, rejectionReason)
|
|
})
|
|
}
|
|
|
|
// TestHookFileWillBeDownloadedHeadRequests tests that HEAD requests trigger the FileWillBeDownloaded hook
|
|
func TestHookFileWillBeDownloadedHeadRequests(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
t.Run("HEAD request to file endpoint triggers hook - rejection", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogInfo("Blocking file download", "file_id", info.Id, "download_type", string(downloadType))
|
|
return "File download blocked by security policy"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file first
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, fileInfo)
|
|
|
|
// Get the file info to pass to the hook
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Call the hook through the app method
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
// Verify the file download was rejected
|
|
assert.NotEmpty(t, rejectionReason)
|
|
assert.Contains(t, rejectionReason, "blocked by security policy")
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("HEAD request to thumbnail endpoint triggers hook - rejection", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
// Only block thumbnail requests
|
|
if downloadType == model.FileDownloadTypeThumbnail {
|
|
p.API.LogInfo("Blocking thumbnail download", "file_id", info.Id)
|
|
return "Thumbnail download blocked"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, fileInfo)
|
|
|
|
// Get the file info
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Call the hook for thumbnail download type through the app method
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeThumbnail)
|
|
|
|
// Verify the thumbnail download was rejected
|
|
assert.NotEmpty(t, rejectionReason)
|
|
assert.Contains(t, rejectionReason, "Thumbnail download blocked")
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("HEAD request to preview endpoint triggers hook - rejection", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
// Only block preview requests
|
|
if downloadType == model.FileDownloadTypePreview {
|
|
p.API.LogInfo("Blocking preview download", "file_id", info.Id)
|
|
return "Preview download blocked"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, fileInfo)
|
|
|
|
// Get the file info
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Create plugin context
|
|
// Call the hook for preview download type through the app method
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypePreview)
|
|
|
|
// Verify the preview download was rejected
|
|
assert.NotEmpty(t, rejectionReason)
|
|
assert.Contains(t, rejectionReason, "Preview download blocked")
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("HEAD request to file endpoint triggers hook - allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogInfo("Allowing file download", "file_id", info.Id, "download_type", string(downloadType))
|
|
return "" // Allow download
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, fileInfo)
|
|
|
|
// Get the file info
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Call the hook through the app method
|
|
rejectionReason := th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
// Verify the file download was allowed
|
|
assert.Empty(t, rejectionReason)
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("HEAD and GET with different download types", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
downloadTypesReceived := []model.FileDownloadType{}
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil).Maybe()
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
|
if args.String(0) == "Hook called" {
|
|
downloadType := args.String(2)
|
|
downloadTypesReceived = append(downloadTypesReceived, model.FileDownloadType(downloadType))
|
|
}
|
|
}).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
mockAPI.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeDownloaded(c *plugin.Context, info *model.FileInfo, userID string, downloadType model.FileDownloadType) string {
|
|
p.API.LogInfo("Hook called", "download_type", string(downloadType))
|
|
return "" // Allow download
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Upload a file
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("test content"), th.BasicChannel.Id, "test.txt")
|
|
require.Nil(t, appErr)
|
|
|
|
info, appErr := th.App.GetFileInfo(th.Context, fileInfo.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Test File download type
|
|
th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeFile)
|
|
|
|
// Test Thumbnail download type
|
|
th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypeThumbnail)
|
|
|
|
// Test Preview download type
|
|
th.App.RunFileWillBeDownloadedHook(th.Context, info, th.BasicUser.Id, "", model.FileDownloadTypePreview)
|
|
|
|
// Verify all three download types were received
|
|
assert.Len(t, downloadTypesReceived, 3)
|
|
assert.Contains(t, downloadTypesReceived, model.FileDownloadTypeFile)
|
|
assert.Contains(t, downloadTypesReceived, model.FileDownloadTypeThumbnail)
|
|
assert.Contains(t, downloadTypesReceived, model.FileDownloadTypePreview)
|
|
|
|
mockAPI.AssertExpectations(t)
|
|
})
|
|
}
|