Merge branch 'master' into MM-47853-true-up-review-telemetry-off-non-air-gapped
Этот коммит содержится в:
@@ -435,7 +435,7 @@ type AppIface interface {
|
||||
BuildPostReactions(ctx request.CTX, postID string) (*[]ReactionImportData, *model.AppError)
|
||||
BuildPushNotificationMessage(c request.CTX, contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError)
|
||||
BuildSamlMetadataObject(idpMetadata []byte) (*model.SamlMetadataResponse, *model.AppError)
|
||||
BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError
|
||||
BulkExport(ctx request.CTX, writer io.Writer, outPath string, job *model.Job, opts model.BulkExportOpts) *model.AppError
|
||||
BulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int) (*model.AppError, int)
|
||||
BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int)
|
||||
CanNotifyAdmin(trial bool) bool
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -65,7 +66,7 @@ var exportablePreferences = map[imports.ComparablePreference]string{{
|
||||
}: "EmailInterval",
|
||||
}
|
||||
|
||||
func (a *App) BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError {
|
||||
func (a *App) BulkExport(ctx request.CTX, writer io.Writer, outPath string, job *model.Job, opts model.BulkExportOpts) *model.AppError {
|
||||
var zipWr *zip.Writer
|
||||
if opts.CreateArchive {
|
||||
var err error
|
||||
@@ -78,46 +79,50 @@ func (a *App) BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts
|
||||
}
|
||||
}
|
||||
|
||||
if job != nil && job.Data == nil {
|
||||
job.Data = make(model.StringMap)
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting version")
|
||||
if err := a.exportVersion(writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting teams")
|
||||
teamNames, err := a.exportAllTeams(writer)
|
||||
teamNames, err := a.exportAllTeams(ctx, job, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting channels")
|
||||
if err = a.exportAllChannels(writer, teamNames); err != nil {
|
||||
if err = a.exportAllChannels(ctx, job, writer, teamNames); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting users")
|
||||
if err = a.exportAllUsers(writer); err != nil {
|
||||
if err = a.exportAllUsers(ctx, job, writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting posts")
|
||||
attachments, err := a.exportAllPosts(ctx, writer, opts.IncludeAttachments)
|
||||
attachments, err := a.exportAllPosts(ctx, job, writer, opts.IncludeAttachments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting emoji")
|
||||
emojiPaths, err := a.exportCustomEmoji(ctx, writer, outPath, "exported_emoji", !opts.CreateArchive)
|
||||
emojiPaths, err := a.exportCustomEmoji(ctx, job, writer, outPath, "exported_emoji", !opts.CreateArchive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting direct channels")
|
||||
if err = a.exportAllDirectChannels(writer); err != nil {
|
||||
if err = a.exportAllDirectChannels(ctx, job, writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger().Info("Bulk export: exporting direct posts")
|
||||
directAttachments, err := a.exportAllDirectPosts(ctx, writer, opts.IncludeAttachments)
|
||||
directAttachments, err := a.exportAllDirectPosts(ctx, job, writer, opts.IncludeAttachments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -139,6 +144,8 @@ func (a *App) BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "attachments_exported", len(attachments)+len(directAttachments)+len(emojiPaths))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -175,9 +182,10 @@ func (a *App) exportVersion(writer io.Writer) *model.AppError {
|
||||
return a.exportWriteLine(writer, versionLine)
|
||||
}
|
||||
|
||||
func (a *App) exportAllTeams(writer io.Writer) (map[string]bool, *model.AppError) {
|
||||
func (a *App) exportAllTeams(ctx request.CTX, job *model.Job, writer io.Writer) (map[string]bool, *model.AppError) {
|
||||
afterId := strings.Repeat("0", 26)
|
||||
teamNames := make(map[string]bool)
|
||||
cnt := 0
|
||||
for {
|
||||
teams, err := a.Srv().Store().Team().GetAllForExportAfter(1000, afterId)
|
||||
if err != nil {
|
||||
@@ -187,6 +195,8 @@ func (a *App) exportAllTeams(writer io.Writer) (map[string]bool, *model.AppError
|
||||
if len(teams) == 0 {
|
||||
break
|
||||
}
|
||||
cnt += len(teams)
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "teams_exported", cnt)
|
||||
|
||||
for _, team := range teams {
|
||||
afterId = team.Id
|
||||
@@ -207,8 +217,9 @@ func (a *App) exportAllTeams(writer io.Writer) (map[string]bool, *model.AppError
|
||||
return teamNames, nil
|
||||
}
|
||||
|
||||
func (a *App) exportAllChannels(writer io.Writer, teamNames map[string]bool) *model.AppError {
|
||||
func (a *App) exportAllChannels(ctx request.CTX, job *model.Job, writer io.Writer, teamNames map[string]bool) *model.AppError {
|
||||
afterId := strings.Repeat("0", 26)
|
||||
cnt := 0
|
||||
for {
|
||||
channels, err := a.Srv().Store().Channel().GetAllChannelsForExportAfter(1000, afterId)
|
||||
|
||||
@@ -219,6 +230,8 @@ func (a *App) exportAllChannels(writer io.Writer, teamNames map[string]bool) *mo
|
||||
if len(channels) == 0 {
|
||||
break
|
||||
}
|
||||
cnt += len(channels)
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "channels_exported", cnt)
|
||||
|
||||
for _, channel := range channels {
|
||||
afterId = channel.Id
|
||||
@@ -242,8 +255,9 @@ func (a *App) exportAllChannels(writer io.Writer, teamNames map[string]bool) *mo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) exportAllUsers(writer io.Writer) *model.AppError {
|
||||
func (a *App) exportAllUsers(ctx request.CTX, job *model.Job, writer io.Writer) *model.AppError {
|
||||
afterId := strings.Repeat("0", 26)
|
||||
cnt := 0
|
||||
for {
|
||||
users, err := a.Srv().Store().User().GetAllAfter(1000, afterId)
|
||||
|
||||
@@ -254,6 +268,8 @@ func (a *App) exportAllUsers(writer io.Writer) *model.AppError {
|
||||
if len(users) == 0 {
|
||||
break
|
||||
}
|
||||
cnt += len(users)
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "users_exported", cnt)
|
||||
|
||||
for _, user := range users {
|
||||
afterId = user.Id
|
||||
@@ -395,12 +411,13 @@ func (a *App) buildUserNotifyProps(notifyProps model.StringMap) *imports.UserNot
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) exportAllPosts(ctx request.CTX, writer io.Writer, withAttachments bool) ([]imports.AttachmentImportData, *model.AppError) {
|
||||
func (a *App) exportAllPosts(ctx request.CTX, job *model.Job, writer io.Writer, withAttachments bool) ([]imports.AttachmentImportData, *model.AppError) {
|
||||
var attachments []imports.AttachmentImportData
|
||||
afterId := strings.Repeat("0", 26)
|
||||
var postProcessCount uint64
|
||||
logCheckpoint := time.Now()
|
||||
|
||||
cnt := 0
|
||||
for {
|
||||
if time.Since(logCheckpoint) > 5*time.Minute {
|
||||
ctx.Logger().Debug(fmt.Sprintf("Bulk Export: processed %d posts", postProcessCount))
|
||||
@@ -415,6 +432,8 @@ func (a *App) exportAllPosts(ctx request.CTX, writer io.Writer, withAttachments
|
||||
if len(posts) == 0 {
|
||||
return attachments, nil
|
||||
}
|
||||
cnt += len(posts)
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "posts_exported", cnt)
|
||||
|
||||
for _, post := range posts {
|
||||
afterId = post.Id
|
||||
@@ -538,9 +557,10 @@ func (a *App) buildPostAttachments(postID string) ([]imports.AttachmentImportDat
|
||||
return attachments, nil
|
||||
}
|
||||
|
||||
func (a *App) exportCustomEmoji(c request.CTX, writer io.Writer, outPath, exportDir string, exportFiles bool) ([]string, *model.AppError) {
|
||||
func (a *App) exportCustomEmoji(c request.CTX, job *model.Job, writer io.Writer, outPath, exportDir string, exportFiles bool) ([]string, *model.AppError) {
|
||||
var emojiPaths []string
|
||||
pageNumber := 0
|
||||
cnt := 0
|
||||
for {
|
||||
customEmojiList, err := a.GetEmojiList(c, pageNumber, 100, model.EmojiSortByName)
|
||||
|
||||
@@ -551,6 +571,8 @@ func (a *App) exportCustomEmoji(c request.CTX, writer io.Writer, outPath, export
|
||||
if len(customEmojiList) == 0 {
|
||||
break
|
||||
}
|
||||
cnt += len(customEmojiList)
|
||||
updateJobProgress(c.Logger(), a.Srv().Store(), job, "emojis_exported", cnt)
|
||||
|
||||
pageNumber++
|
||||
|
||||
@@ -619,8 +641,9 @@ func (a *App) copyEmojiImages(emojiId string, emojiImagePath string, pathToDir s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) exportAllDirectChannels(writer io.Writer) *model.AppError {
|
||||
func (a *App) exportAllDirectChannels(ctx request.CTX, job *model.Job, writer io.Writer) *model.AppError {
|
||||
afterId := strings.Repeat("0", 26)
|
||||
cnt := 0
|
||||
for {
|
||||
channels, err := a.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, afterId)
|
||||
if err != nil {
|
||||
@@ -630,6 +653,8 @@ func (a *App) exportAllDirectChannels(writer io.Writer) *model.AppError {
|
||||
if len(channels) == 0 {
|
||||
break
|
||||
}
|
||||
cnt += len(channels)
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "direct_channels_exported", cnt)
|
||||
|
||||
for _, channel := range channels {
|
||||
afterId = channel.Id
|
||||
@@ -682,12 +707,13 @@ func (a *App) buildFavoritedByList(channelID string) ([]string, *model.AppError)
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func (a *App) exportAllDirectPosts(ctx request.CTX, writer io.Writer, withAttachments bool) ([]imports.AttachmentImportData, *model.AppError) {
|
||||
func (a *App) exportAllDirectPosts(ctx request.CTX, job *model.Job, writer io.Writer, withAttachments bool) ([]imports.AttachmentImportData, *model.AppError) {
|
||||
var attachments []imports.AttachmentImportData
|
||||
afterId := strings.Repeat("0", 26)
|
||||
var postProcessCount uint64
|
||||
logCheckpoint := time.Now()
|
||||
|
||||
cnt := 0
|
||||
for {
|
||||
if time.Since(logCheckpoint) > 5*time.Minute {
|
||||
ctx.Logger().Debug(fmt.Sprintf("Bulk Export: processed %d direct posts", postProcessCount))
|
||||
@@ -702,6 +728,8 @@ func (a *App) exportAllDirectPosts(ctx request.CTX, writer io.Writer, withAttach
|
||||
if len(posts) == 0 {
|
||||
break
|
||||
}
|
||||
cnt += len(posts)
|
||||
updateJobProgress(ctx.Logger(), a.Srv().Store(), job, "direct_posts_exported", cnt)
|
||||
|
||||
for _, post := range posts {
|
||||
afterId = post.Id
|
||||
@@ -815,3 +843,12 @@ func (a *App) DeleteExport(name string) *model.AppError {
|
||||
|
||||
return a.RemoveFile(filePath)
|
||||
}
|
||||
|
||||
func updateJobProgress(logger mlog.LoggerIFace, store store.Store, job *model.Job, key string, value int) {
|
||||
if job != nil {
|
||||
job.Data[key] = strconv.Itoa(value)
|
||||
if _, err2 := store.Job().UpdateOptimistically(job, model.JobStatusInProgress); err2 != nil {
|
||||
logger.Warn("Failed to update job status", mlog.Err(err2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestExportCustomEmoji(t *testing.T) {
|
||||
outPath, err := filepath.Abs(filePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, appErr := th.App.exportCustomEmoji(th.Context, fileWriter, outPath, dirNameToExportEmoji, false)
|
||||
_, appErr := th.App.exportCustomEmoji(th.Context, nil, fileWriter, outPath, dirNameToExportEmoji, false)
|
||||
require.Nil(t, appErr, "should not have failed")
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ func TestExportAllUsers(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
|
||||
var b bytes.Buffer
|
||||
err = th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
th2 := Setup(t)
|
||||
@@ -235,7 +235,7 @@ func TestExportDMChannel(t *testing.T) {
|
||||
})
|
||||
|
||||
var b bytes.Buffer
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
||||
@@ -282,7 +282,7 @@ func TestExportDMChannel(t *testing.T) {
|
||||
th1.App.PermanentDeleteUser(th1.Context, th1.BasicUser)
|
||||
|
||||
var b bytes.Buffer
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
th2 := Setup(t).InitBasic()
|
||||
@@ -306,7 +306,7 @@ func TestExportDMChannelToSelf(t *testing.T) {
|
||||
th1.CreateDmChannel(th1.BasicUser)
|
||||
|
||||
var b bytes.Buffer
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
||||
@@ -344,7 +344,7 @@ func TestExportGMChannel(t *testing.T) {
|
||||
th1.CreateGroupChannel(th1.Context, user1, user2)
|
||||
|
||||
var b bytes.Buffer
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
||||
@@ -376,7 +376,7 @@ func TestExportGMandDMChannels(t *testing.T) {
|
||||
th1.CreateGroupChannel(th1.Context, user1, user2)
|
||||
|
||||
var b bytes.Buffer
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
||||
@@ -459,7 +459,7 @@ func TestExportDMandGMPost(t *testing.T) {
|
||||
assert.Equal(t, 4, len(posts))
|
||||
|
||||
var b bytes.Buffer
|
||||
appErr := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
appErr := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th1.TearDown()
|
||||
@@ -534,7 +534,7 @@ func TestExportPostWithProps(t *testing.T) {
|
||||
require.NotEmpty(t, posts[1].Props)
|
||||
|
||||
var b bytes.Buffer
|
||||
appErr := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
appErr := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th1.TearDown()
|
||||
@@ -572,7 +572,7 @@ func TestExportDMPostWithSelf(t *testing.T) {
|
||||
th1.CreatePost(dmChannel)
|
||||
|
||||
var b bytes.Buffer
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
posts, nErr := th1.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000")
|
||||
@@ -640,7 +640,7 @@ func TestBulkExport(t *testing.T) {
|
||||
IncludeAttachments: true,
|
||||
CreateArchive: true,
|
||||
}
|
||||
appErr = th.App.BulkExport(th.Context, exportFile, dir, opts)
|
||||
appErr = th.App.BulkExport(th.Context, exportFile, dir, nil, opts)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.TearDown()
|
||||
@@ -731,7 +731,7 @@ func TestExportDeletedTeams(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
|
||||
var b bytes.Buffer
|
||||
err = th1.App.BulkExport(th1.Context, &b, "somePath", model.BulkExportOpts{})
|
||||
err = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||
require.Nil(t, err)
|
||||
|
||||
th2 := Setup(t)
|
||||
|
||||
16
app/file.go
16
app/file.go
@@ -178,22 +178,8 @@ func (s *Server) writeFile(fr io.Reader, path string) (int64, *model.AppError) {
|
||||
}
|
||||
|
||||
func (s *Server) writeFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
|
||||
type ContextWriter interface {
|
||||
WriteFileContext(context.Context, io.Reader, string) (int64, error)
|
||||
}
|
||||
|
||||
var (
|
||||
fileBackend = s.FileBackend()
|
||||
written int64
|
||||
err error
|
||||
)
|
||||
|
||||
// Check if we can provide a custom context, otherwise just use the default method.
|
||||
if cw, ok := fileBackend.(ContextWriter); ok {
|
||||
written, err = cw.WriteFileContext(ctx, fr, path)
|
||||
} else {
|
||||
written, err = fileBackend.WriteFile(fr, path)
|
||||
}
|
||||
written, err := filestore.TryWriteFileContext(s.FileBackend(), ctx, fr, path)
|
||||
if err != nil {
|
||||
return written, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -974,7 +974,7 @@ func (a *OpenTracingAppLayer) BuildSamlMetadataObject(idpMetadata []byte) (*mode
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) BulkExport(ctx request.CTX, writer io.Writer, outPath string, opts model.BulkExportOpts) *model.AppError {
|
||||
func (a *OpenTracingAppLayer) BulkExport(ctx request.CTX, writer io.Writer, outPath string, job *model.Job, opts model.BulkExportOpts) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.BulkExport")
|
||||
|
||||
@@ -986,7 +986,7 @@ func (a *OpenTracingAppLayer) BulkExport(ctx request.CTX, writer io.Writer, outP
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.BulkExport(ctx, writer, outPath, opts)
|
||||
resultVar0 := a.app.BulkExport(ctx, writer, outPath, job, opts)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
|
||||
@@ -141,6 +141,7 @@ func TestMetrics(t *testing.T) {
|
||||
mockMetricsImpl := &mocks.MetricsInterface{}
|
||||
mockMetricsImpl.On("Register").Return()
|
||||
mockMetricsImpl.On("ObserveStoreMethodDuration", mock.Anything, mock.Anything, mock.Anything).Return()
|
||||
mockMetricsImpl.On("RegisterDBCollector", mock.AnythingOfType("*sql.DB"), "master")
|
||||
|
||||
th := Setup(t, StartMetrics(), func(ps *PlatformService) error {
|
||||
ps.metricsIFace = mockMetricsImpl
|
||||
|
||||
@@ -130,23 +130,6 @@ func (ch *Channels) syncPluginsActiveState() {
|
||||
}
|
||||
|
||||
if pluginEnabled {
|
||||
// Disable focalboard in product mode.
|
||||
if pluginID == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct {
|
||||
msg := "Plugin cannot run in product mode. Disabling."
|
||||
mlog.Warn(msg, mlog.String("plugin_id", model.PluginIdFocalboard))
|
||||
|
||||
// This is a mini-version of ch.disablePlugin.
|
||||
// We don't call that directly, because that will recursively call
|
||||
// this method.
|
||||
ch.cfgSvc.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{Enable: false}
|
||||
})
|
||||
pluginsEnvironment.SetPluginError(pluginID, msg)
|
||||
ch.unregisterPluginCommands(pluginID)
|
||||
disabledPlugins = append(disabledPlugins, plugin)
|
||||
continue
|
||||
}
|
||||
|
||||
enabledPlugins = append(enabledPlugins, plugin)
|
||||
} else {
|
||||
disabledPlugins = append(disabledPlugins, plugin)
|
||||
@@ -322,6 +305,16 @@ func (ch *Channels) syncPlugins() *model.AppError {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, plugin := range availablePlugins {
|
||||
// Disable focalboard in product mode.
|
||||
if plugin.Manifest.Id == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct {
|
||||
mlog.Info("Plugin cannot run in product mode, disabling.", mlog.String("plugin_id", model.PluginIdFocalboard))
|
||||
appErr := ch.disablePlugin(model.PluginIdFocalboard)
|
||||
if appErr != nil {
|
||||
mlog.Error("Error disabling plugin", mlog.Err(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(pluginID string) {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -5,7 +5,6 @@ package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
@@ -93,7 +92,7 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
// Should be in the form of /$PLUGIN_ID/public/{anything} by the time we get here
|
||||
// Should be in the form of /(subpath/)?/plugins/{plugin_id}/public/* by the time we get here
|
||||
vars := mux.Vars(r)
|
||||
pluginID := vars["plugin_id"]
|
||||
|
||||
@@ -111,8 +110,13 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
subpath, err := utils.GetSubpathFromConfig(ch.cfgSvc.Config())
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
publicFilePath := path.Clean(r.URL.Path)
|
||||
prefix := fmt.Sprintf("/plugins/%s/public/", pluginID)
|
||||
prefix := path.Join(subpath, "plugins", pluginID, "public")
|
||||
if !strings.HasPrefix(publicFilePath, prefix) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
|
||||
@@ -4,23 +4,65 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
|
||||
)
|
||||
|
||||
func TestServePluginPublicRequest(t *testing.T) {
|
||||
installPlugin := func(t *testing.T, th *TestHelper, pluginID string) {
|
||||
t.Helper()
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
fileReader, err := os.Open(filepath.Join(path, fmt.Sprintf("%s.tar.gz", pluginID)))
|
||||
require.NoError(t, err)
|
||||
defer fileReader.Close()
|
||||
|
||||
_, appErr := th.App.WriteFile(fileReader, getBundleStorePath(pluginID))
|
||||
checkNoError(t, appErr)
|
||||
|
||||
appErr = th.App.SyncPlugins()
|
||||
checkNoError(t, appErr)
|
||||
|
||||
env := th.App.GetPluginsEnvironment()
|
||||
require.NotNil(t, env)
|
||||
|
||||
// Check if installed
|
||||
pluginStatus, err := env.Statuses()
|
||||
require.NoError(t, err)
|
||||
found := false
|
||||
for _, pluginStatus := range pluginStatus {
|
||||
if pluginStatus.PluginId == pluginID {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.True(t, found, "failed to find plugin %s in plugin statuses", pluginID)
|
||||
|
||||
appErr = th.App.EnablePlugin(pluginID)
|
||||
checkNoError(t, appErr)
|
||||
|
||||
t.Cleanup(func() {
|
||||
appErr = th.App.ch.RemovePlugin(pluginID)
|
||||
checkNoError(t, appErr)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("returns not found when plugins environment is nil", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
t.Cleanup(th.TearDown)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
req, err := http.NewRequest("GET", "/plugins", nil)
|
||||
req, err := http.NewRequest("GET", "/plugins/plugin_id/public/file.txt", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -29,4 +71,83 @@ func TestServePluginPublicRequest(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("resolves path for valid plugin", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
t.Cleanup(th.TearDown)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
fileReader, err := os.Open(filepath.Join(path, "testplugin.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
defer fileReader.Close()
|
||||
|
||||
installPlugin(t, th, "testplugin")
|
||||
|
||||
req, err := http.NewRequest("GET", "/plugins/testplugin/public/file.txt", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
th.App.ch.srv.Router.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Hello World!", string(body))
|
||||
})
|
||||
|
||||
t.Run("resolves path for valid plugin when subpath configured", func(t *testing.T) {
|
||||
os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://localhost:8065/subpath")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
|
||||
|
||||
th := Setup(t)
|
||||
t.Cleanup(th.TearDown)
|
||||
|
||||
installPlugin(t, th, "testplugin")
|
||||
|
||||
req, err := http.NewRequest("GET", "/subpath/plugins/testplugin/public/file.txt", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
th.App.ch.srv.RootRouter.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
body, err := io.ReadAll(rr.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hello World!", string(body))
|
||||
})
|
||||
|
||||
t.Run("fails for invalid plugin", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
t.Cleanup(th.TearDown)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
req, err := http.NewRequest("GET", "/plugins/invalidplugin/public/file.txt", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
th.App.ch.srv.Router.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("fails attempting to break out of path", func(t *testing.T) {
|
||||
os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://localhost:8065/subpath")
|
||||
defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
|
||||
|
||||
th := Setup(t)
|
||||
t.Cleanup(th.TearDown)
|
||||
|
||||
installPlugin(t, th, "testplugin")
|
||||
installPlugin(t, th, "testplugin2")
|
||||
|
||||
req, err := http.NewRequest("GET", "/subpath/plugins/testplugin/public/../../testplugin2/file.txt", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
th.App.ch.srv.RootRouter.ServeHTTP(rr, req)
|
||||
|
||||
require.Equal(t, http.StatusMovedPermanently, rr.Code)
|
||||
assert.Equal(t, "/subpath/plugins/testplugin2/file.txt", rr.Header()["Location"][0])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2409,10 +2409,17 @@ func TestFollowThreadSkipsParticipants(t *testing.T) {
|
||||
require.True(t, p.Id == sysadmin.Id || p.Id == user.Id)
|
||||
}
|
||||
|
||||
oldID := threadMembership.PostId
|
||||
threadMembership.PostId = "notfound"
|
||||
_, err = th.App.GetThreadForUser(threadMembership, false)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, err.StatusCode)
|
||||
|
||||
threadMembership.Following = false
|
||||
threadMembership.PostId = oldID
|
||||
_, err = th.App.GetThreadForUser(threadMembership, false)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, err.StatusCode)
|
||||
}
|
||||
|
||||
func TestAutofollowBasedOnRootPost(t *testing.T) {
|
||||
|
||||
@@ -290,8 +290,9 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
}
|
||||
return event
|
||||
},
|
||||
TracesSampler: sentry.TracesSamplerFunc(func(ctx sentry.SamplingContext) sentry.Sampled {
|
||||
return sentry.SampledFalse
|
||||
EnableTracing: false,
|
||||
TracesSampler: sentry.TracesSampler(func(ctx sentry.SamplingContext) float64 {
|
||||
return 0.0
|
||||
}),
|
||||
}); err2 != nil {
|
||||
mlog.Warn("Sentry could not be initiated, probably bad DSN?", mlog.Err(err2))
|
||||
|
||||
@@ -102,7 +102,6 @@ func TestAddUserToTeam(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("block user by domain but allow bot", func(t *testing.T) {
|
||||
t.Skip("MM-48973")
|
||||
th.BasicTeam.AllowedDomains = "example.com"
|
||||
_, err := th.App.UpdateTeam(th.BasicTeam)
|
||||
require.Nil(t, err, "Should update the team")
|
||||
|
||||
14
app/user.go
14
app/user.go
@@ -321,12 +321,14 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m
|
||||
// table in CWS. This is then used to calculate how much the customers have to pay in addition for the extra users. If the
|
||||
// workspace is currently on a monthly plan, then this function will not do anything.
|
||||
|
||||
go func() {
|
||||
_, err := a.SendSubscriptionHistoryEvent(ruser.Id)
|
||||
if err != nil {
|
||||
c.Logger().Error("Failed to create/update the SubscriptionHistoryEvent", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
if a.Channels().License().IsCloud() {
|
||||
go func(userId string) {
|
||||
_, err := a.SendSubscriptionHistoryEvent(userId)
|
||||
if err != nil {
|
||||
c.Logger().Error("Failed to create/update the SubscriptionHistoryEvent", mlog.Err(err))
|
||||
}
|
||||
}(ruser.Id)
|
||||
}
|
||||
|
||||
return ruser, nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user