github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/plugin_hooks_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"path/filepath"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/pkg/errors"
    18  
    19  	"github.com/mattermost/mattermost-server/v5/einterfaces/mocks"
    20  	"github.com/mattermost/mattermost-server/v5/model"
    21  	"github.com/mattermost/mattermost-server/v5/plugin"
    22  	"github.com/mattermost/mattermost-server/v5/plugin/plugintest"
    23  	"github.com/mattermost/mattermost-server/v5/utils"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/mock"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) {
    31  	pluginDir, err := ioutil.TempDir("", "")
    32  	require.NoError(t, err)
    33  	webappPluginDir, err := ioutil.TempDir("", "")
    34  	require.NoError(t, err)
    35  
    36  	env, err := plugin.NewEnvironment(apiFunc, pluginDir, webappPluginDir, app.Log(), nil)
    37  	require.NoError(t, err)
    38  
    39  	app.SetPluginsEnvironment(env)
    40  	pluginIds := []string{}
    41  	activationErrors := []error{}
    42  	for _, code := range pluginCode {
    43  		pluginId := model.NewId()
    44  		backend := filepath.Join(pluginDir, pluginId, "backend.exe")
    45  		utils.CompileGo(t, code, backend)
    46  
    47  		ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(`{"id": "`+pluginId+`", "backend": {"executable": "backend.exe"}}`), 0600)
    48  		_, _, activationErr := env.Activate(pluginId)
    49  		pluginIds = append(pluginIds, pluginId)
    50  		activationErrors = append(activationErrors, activationErr)
    51  
    52  		app.UpdateConfig(func(cfg *model.Config) {
    53  			cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{
    54  				Enable: true,
    55  			}
    56  		})
    57  	}
    58  
    59  	return func() {
    60  		os.RemoveAll(pluginDir)
    61  		os.RemoveAll(webappPluginDir)
    62  	}, pluginIds, activationErrors
    63  }
    64  
    65  func TestHookMessageWillBePosted(t *testing.T) {
    66  	t.Run("rejected", func(t *testing.T) {
    67  		th := Setup(t).InitBasic()
    68  		defer th.TearDown()
    69  
    70  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
    71  			`
    72  			package main
    73  
    74  			import (
    75  				"github.com/mattermost/mattermost-server/v5/plugin"
    76  				"github.com/mattermost/mattermost-server/v5/model"
    77  			)
    78  
    79  			type MyPlugin struct {
    80  				plugin.MattermostPlugin
    81  			}
    82  
    83  			func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
    84  				return nil, "rejected"
    85  			}
    86  
    87  			func main() {
    88  				plugin.ClientMain(&MyPlugin{})
    89  			}
    90  			`,
    91  		}, th.App, th.App.NewPluginAPI)
    92  		defer tearDown()
    93  
    94  		post := &model.Post{
    95  			UserId:    th.BasicUser.Id,
    96  			ChannelId: th.BasicChannel.Id,
    97  			Message:   "message_",
    98  			CreateAt:  model.GetMillis() - 10000,
    99  		}
   100  		_, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   101  		if assert.NotNil(t, err) {
   102  			assert.Equal(t, "Post rejected by plugin. rejected", err.Message)
   103  		}
   104  	})
   105  
   106  	t.Run("rejected, returned post ignored", func(t *testing.T) {
   107  		th := Setup(t).InitBasic()
   108  		defer th.TearDown()
   109  
   110  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   111  			`
   112  			package main
   113  
   114  			import (
   115  				"github.com/mattermost/mattermost-server/v5/plugin"
   116  				"github.com/mattermost/mattermost-server/v5/model"
   117  			)
   118  
   119  			type MyPlugin struct {
   120  				plugin.MattermostPlugin
   121  			}
   122  
   123  			func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
   124  				post.Message = "ignored"
   125  				return post, "rejected"
   126  			}
   127  
   128  			func main() {
   129  				plugin.ClientMain(&MyPlugin{})
   130  			}
   131  			`,
   132  		}, th.App, th.App.NewPluginAPI)
   133  		defer tearDown()
   134  
   135  		post := &model.Post{
   136  			UserId:    th.BasicUser.Id,
   137  			ChannelId: th.BasicChannel.Id,
   138  			Message:   "message_",
   139  			CreateAt:  model.GetMillis() - 10000,
   140  		}
   141  		_, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   142  		if assert.NotNil(t, err) {
   143  			assert.Equal(t, "Post rejected by plugin. rejected", err.Message)
   144  		}
   145  	})
   146  
   147  	t.Run("allowed", func(t *testing.T) {
   148  		th := Setup(t).InitBasic()
   149  		defer th.TearDown()
   150  
   151  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   152  			`
   153  			package main
   154  
   155  			import (
   156  				"github.com/mattermost/mattermost-server/v5/plugin"
   157  				"github.com/mattermost/mattermost-server/v5/model"
   158  			)
   159  
   160  			type MyPlugin struct {
   161  				plugin.MattermostPlugin
   162  			}
   163  
   164  			func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
   165  				return nil, ""
   166  			}
   167  
   168  			func main() {
   169  				plugin.ClientMain(&MyPlugin{})
   170  			}
   171  			`,
   172  		}, th.App, th.App.NewPluginAPI)
   173  		defer tearDown()
   174  
   175  		post := &model.Post{
   176  			UserId:    th.BasicUser.Id,
   177  			ChannelId: th.BasicChannel.Id,
   178  			Message:   "message",
   179  			CreateAt:  model.GetMillis() - 10000,
   180  		}
   181  		post, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   182  		require.Nil(t, err)
   183  
   184  		assert.Equal(t, "message", post.Message)
   185  		retrievedPost, errSingle := th.App.Srv().Store.Post().GetSingle(post.Id)
   186  		require.Nil(t, errSingle)
   187  		assert.Equal(t, "message", retrievedPost.Message)
   188  	})
   189  
   190  	t.Run("updated", func(t *testing.T) {
   191  		th := Setup(t).InitBasic()
   192  		defer th.TearDown()
   193  
   194  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   195  			`
   196  			package main
   197  
   198  			import (
   199  				"github.com/mattermost/mattermost-server/v5/plugin"
   200  				"github.com/mattermost/mattermost-server/v5/model"
   201  			)
   202  
   203  			type MyPlugin struct {
   204  				plugin.MattermostPlugin
   205  			}
   206  
   207  			func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
   208  				post.Message = post.Message + "_fromplugin"
   209  				return post, ""
   210  			}
   211  
   212  			func main() {
   213  				plugin.ClientMain(&MyPlugin{})
   214  			}
   215  			`,
   216  		}, th.App, th.App.NewPluginAPI)
   217  		defer tearDown()
   218  
   219  		post := &model.Post{
   220  			UserId:    th.BasicUser.Id,
   221  			ChannelId: th.BasicChannel.Id,
   222  			Message:   "message",
   223  			CreateAt:  model.GetMillis() - 10000,
   224  		}
   225  		post, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   226  		require.Nil(t, err)
   227  
   228  		assert.Equal(t, "message_fromplugin", post.Message)
   229  		retrievedPost, errSingle := th.App.Srv().Store.Post().GetSingle(post.Id)
   230  		require.Nil(t, errSingle)
   231  		assert.Equal(t, "message_fromplugin", retrievedPost.Message)
   232  	})
   233  
   234  	t.Run("multiple updated", func(t *testing.T) {
   235  		th := Setup(t).InitBasic()
   236  		defer th.TearDown()
   237  
   238  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   239  			`
   240  			package main
   241  
   242  			import (
   243  				"github.com/mattermost/mattermost-server/v5/plugin"
   244  				"github.com/mattermost/mattermost-server/v5/model"
   245  			)
   246  
   247  			type MyPlugin struct {
   248  				plugin.MattermostPlugin
   249  			}
   250  
   251  			func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
   252  
   253  				post.Message = "prefix_" + post.Message
   254  				return post, ""
   255  			}
   256  
   257  			func main() {
   258  				plugin.ClientMain(&MyPlugin{})
   259  			}
   260  			`,
   261  			`
   262  			package main
   263  
   264  			import (
   265  				"github.com/mattermost/mattermost-server/v5/plugin"
   266  				"github.com/mattermost/mattermost-server/v5/model"
   267  			)
   268  
   269  			type MyPlugin struct {
   270  				plugin.MattermostPlugin
   271  			}
   272  
   273  			func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
   274  				post.Message = post.Message + "_suffix"
   275  				return post, ""
   276  			}
   277  
   278  			func main() {
   279  				plugin.ClientMain(&MyPlugin{})
   280  			}
   281  			`,
   282  		}, th.App, th.App.NewPluginAPI)
   283  		defer tearDown()
   284  
   285  		post := &model.Post{
   286  			UserId:    th.BasicUser.Id,
   287  			ChannelId: th.BasicChannel.Id,
   288  			Message:   "message",
   289  			CreateAt:  model.GetMillis() - 10000,
   290  		}
   291  		post, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   292  		require.Nil(t, err)
   293  		assert.Equal(t, "prefix_message_suffix", post.Message)
   294  	})
   295  }
   296  
   297  func TestHookMessageHasBeenPosted(t *testing.T) {
   298  	th := Setup(t).InitBasic()
   299  	defer th.TearDown()
   300  
   301  	var mockAPI plugintest.API
   302  	mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   303  	mockAPI.On("LogDebug", "message").Return(nil)
   304  
   305  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   306  		[]string{
   307  			`
   308  		package main
   309  
   310  		import (
   311  			"github.com/mattermost/mattermost-server/v5/plugin"
   312  			"github.com/mattermost/mattermost-server/v5/model"
   313  		)
   314  
   315  		type MyPlugin struct {
   316  			plugin.MattermostPlugin
   317  		}
   318  
   319  		func (p *MyPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
   320  			p.API.LogDebug(post.Message)
   321  		}
   322  
   323  		func main() {
   324  			plugin.ClientMain(&MyPlugin{})
   325  		}
   326  	`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   327  	defer tearDown()
   328  
   329  	post := &model.Post{
   330  		UserId:    th.BasicUser.Id,
   331  		ChannelId: th.BasicChannel.Id,
   332  		Message:   "message",
   333  		CreateAt:  model.GetMillis() - 10000,
   334  	}
   335  	_, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   336  	require.Nil(t, err)
   337  }
   338  
   339  func TestHookMessageWillBeUpdated(t *testing.T) {
   340  	th := Setup(t).InitBasic()
   341  	defer th.TearDown()
   342  
   343  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   344  		[]string{
   345  			`
   346  		package main
   347  
   348  		import (
   349  			"github.com/mattermost/mattermost-server/v5/plugin"
   350  			"github.com/mattermost/mattermost-server/v5/model"
   351  		)
   352  
   353  		type MyPlugin struct {
   354  			plugin.MattermostPlugin
   355  		}
   356  
   357  		func (p *MyPlugin) MessageWillBeUpdated(c *plugin.Context, newPost, oldPost *model.Post) (*model.Post, string) {
   358  			newPost.Message = newPost.Message + "fromplugin"
   359  			return newPost, ""
   360  		}
   361  
   362  		func main() {
   363  			plugin.ClientMain(&MyPlugin{})
   364  		}
   365  	`}, th.App, th.App.NewPluginAPI)
   366  	defer tearDown()
   367  
   368  	post := &model.Post{
   369  		UserId:    th.BasicUser.Id,
   370  		ChannelId: th.BasicChannel.Id,
   371  		Message:   "message_",
   372  		CreateAt:  model.GetMillis() - 10000,
   373  	}
   374  	post, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   375  	require.Nil(t, err)
   376  	assert.Equal(t, "message_", post.Message)
   377  	post.Message = post.Message + "edited_"
   378  	post, err = th.App.UpdatePost(post, true)
   379  	require.Nil(t, err)
   380  	assert.Equal(t, "message_edited_fromplugin", post.Message)
   381  }
   382  
   383  func TestHookMessageHasBeenUpdated(t *testing.T) {
   384  	th := Setup(t).InitBasic()
   385  	defer th.TearDown()
   386  
   387  	var mockAPI plugintest.API
   388  	mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   389  	mockAPI.On("LogDebug", "message_edited").Return(nil)
   390  	mockAPI.On("LogDebug", "message_").Return(nil)
   391  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   392  		[]string{
   393  			`
   394  		package main
   395  
   396  		import (
   397  			"github.com/mattermost/mattermost-server/v5/plugin"
   398  			"github.com/mattermost/mattermost-server/v5/model"
   399  		)
   400  
   401  		type MyPlugin struct {
   402  			plugin.MattermostPlugin
   403  		}
   404  
   405  		func (p *MyPlugin) MessageHasBeenUpdated(c *plugin.Context, newPost, oldPost *model.Post) {
   406  			p.API.LogDebug(newPost.Message)
   407  			p.API.LogDebug(oldPost.Message)
   408  		}
   409  
   410  		func main() {
   411  			plugin.ClientMain(&MyPlugin{})
   412  		}
   413  	`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   414  	defer tearDown()
   415  
   416  	post := &model.Post{
   417  		UserId:    th.BasicUser.Id,
   418  		ChannelId: th.BasicChannel.Id,
   419  		Message:   "message_",
   420  		CreateAt:  model.GetMillis() - 10000,
   421  	}
   422  	post, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   423  	require.Nil(t, err)
   424  	assert.Equal(t, "message_", post.Message)
   425  	post.Message = post.Message + "edited"
   426  	_, err = th.App.UpdatePost(post, true)
   427  	require.Nil(t, err)
   428  }
   429  
   430  func TestHookFileWillBeUploaded(t *testing.T) {
   431  	t.Run("rejected", func(t *testing.T) {
   432  		th := Setup(t).InitBasic()
   433  		defer th.TearDown()
   434  
   435  		var mockAPI plugintest.API
   436  		mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   437  		mockAPI.On("LogDebug", "testhook.txt").Return(nil)
   438  		mockAPI.On("LogDebug", "inputfile").Return(nil)
   439  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   440  			`
   441  			package main
   442  
   443  			import (
   444  				"io"
   445  				"github.com/mattermost/mattermost-server/v5/plugin"
   446  				"github.com/mattermost/mattermost-server/v5/model"
   447  			)
   448  
   449  			type MyPlugin struct {
   450  				plugin.MattermostPlugin
   451  			}
   452  
   453  			func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
   454  				return nil, "rejected"
   455  			}
   456  
   457  			func main() {
   458  				plugin.ClientMain(&MyPlugin{})
   459  			}
   460  			`,
   461  		}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   462  		defer tearDown()
   463  
   464  		_, err := th.App.UploadFiles(
   465  			"noteam",
   466  			th.BasicChannel.Id,
   467  			th.BasicUser.Id,
   468  			[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
   469  			[]string{"testhook.txt"},
   470  			[]string{},
   471  			time.Now(),
   472  		)
   473  		if assert.NotNil(t, err) {
   474  			assert.Equal(t, "File rejected by plugin. rejected", err.Message)
   475  		}
   476  	})
   477  
   478  	t.Run("rejected, returned file ignored", func(t *testing.T) {
   479  		th := Setup(t).InitBasic()
   480  		defer th.TearDown()
   481  
   482  		var mockAPI plugintest.API
   483  		mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   484  		mockAPI.On("LogDebug", "testhook.txt").Return(nil)
   485  		mockAPI.On("LogDebug", "inputfile").Return(nil)
   486  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   487  			`
   488  			package main
   489  
   490  			import (
   491  				"fmt"
   492  				"io"
   493  				"github.com/mattermost/mattermost-server/v5/plugin"
   494  				"github.com/mattermost/mattermost-server/v5/model"
   495  			)
   496  
   497  			type MyPlugin struct {
   498  				plugin.MattermostPlugin
   499  			}
   500  
   501  			func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
   502  				n, err := output.Write([]byte("ignored"))
   503  				if err != nil {
   504  					return info, fmt.Sprintf("FAILED to write output file n: %v, err: %v", n, err)
   505  				}
   506  				info.Name = "ignored"
   507  				return info, "rejected"
   508  			}
   509  
   510  			func main() {
   511  				plugin.ClientMain(&MyPlugin{})
   512  			}
   513  			`,
   514  		}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   515  		defer tearDown()
   516  
   517  		_, err := th.App.UploadFiles(
   518  			"noteam",
   519  			th.BasicChannel.Id,
   520  			th.BasicUser.Id,
   521  			[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
   522  			[]string{"testhook.txt"},
   523  			[]string{},
   524  			time.Now(),
   525  		)
   526  		if assert.NotNil(t, err) {
   527  			assert.Equal(t, "File rejected by plugin. rejected", err.Message)
   528  		}
   529  	})
   530  
   531  	t.Run("allowed", func(t *testing.T) {
   532  		th := Setup(t).InitBasic()
   533  		defer th.TearDown()
   534  
   535  		var mockAPI plugintest.API
   536  		mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   537  		mockAPI.On("LogDebug", "testhook.txt").Return(nil)
   538  		mockAPI.On("LogDebug", "inputfile").Return(nil)
   539  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   540  			`
   541  			package main
   542  
   543  			import (
   544  				"io"
   545  				"github.com/mattermost/mattermost-server/v5/plugin"
   546  				"github.com/mattermost/mattermost-server/v5/model"
   547  			)
   548  
   549  			type MyPlugin struct {
   550  				plugin.MattermostPlugin
   551  			}
   552  
   553  			func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
   554  				return nil, ""
   555  			}
   556  
   557  			func main() {
   558  				plugin.ClientMain(&MyPlugin{})
   559  			}
   560  			`,
   561  		}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   562  		defer tearDown()
   563  
   564  		response, err := th.App.UploadFiles(
   565  			"noteam",
   566  			th.BasicChannel.Id,
   567  			th.BasicUser.Id,
   568  			[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
   569  			[]string{"testhook.txt"},
   570  			[]string{},
   571  			time.Now(),
   572  		)
   573  		assert.Nil(t, err)
   574  		assert.NotNil(t, response)
   575  		assert.Equal(t, 1, len(response.FileInfos))
   576  
   577  		fileId := response.FileInfos[0].Id
   578  		fileInfo, err := th.App.GetFileInfo(fileId)
   579  		assert.Nil(t, err)
   580  		assert.NotNil(t, fileInfo)
   581  		assert.Equal(t, "testhook.txt", fileInfo.Name)
   582  
   583  		fileReader, err := th.App.FileReader(fileInfo.Path)
   584  		assert.Nil(t, err)
   585  		var resultBuf bytes.Buffer
   586  		io.Copy(&resultBuf, fileReader)
   587  		assert.Equal(t, "inputfile", resultBuf.String())
   588  	})
   589  
   590  	t.Run("updated", func(t *testing.T) {
   591  		th := Setup(t).InitBasic()
   592  		defer th.TearDown()
   593  
   594  		var mockAPI plugintest.API
   595  		mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   596  		mockAPI.On("LogDebug", "testhook.txt").Return(nil)
   597  		mockAPI.On("LogDebug", "inputfile").Return(nil)
   598  		tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
   599  			`
   600  			package main
   601  
   602  			import (
   603  				"io"
   604  				"fmt"
   605  				"bytes"
   606  				"github.com/mattermost/mattermost-server/v5/plugin"
   607  				"github.com/mattermost/mattermost-server/v5/model"
   608  			)
   609  
   610  			type MyPlugin struct {
   611  				plugin.MattermostPlugin
   612  			}
   613  
   614  			func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
   615  				var buf bytes.Buffer
   616  				n, err := buf.ReadFrom(file)
   617  				if err != nil {
   618  					panic(fmt.Sprintf("buf.ReadFrom failed, reading %d bytes: %s", err.Error()))
   619  				}
   620  
   621  				outbuf := bytes.NewBufferString("changedtext")
   622  				n, err = io.Copy(output, outbuf)
   623  				if err != nil {
   624  					panic(fmt.Sprintf("io.Copy failed after %d bytes: %s", n, err.Error()))
   625  				}
   626  				if n != 11 {
   627  					panic(fmt.Sprintf("io.Copy only copied %d bytes", n))
   628  				}
   629  				info.Name = "modifiedinfo"
   630  				return info, ""
   631  			}
   632  
   633  			func main() {
   634  				plugin.ClientMain(&MyPlugin{})
   635  			}
   636  			`,
   637  		}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   638  		defer tearDown()
   639  
   640  		response, err := th.App.UploadFiles(
   641  			"noteam",
   642  			th.BasicChannel.Id,
   643  			th.BasicUser.Id,
   644  			[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
   645  			[]string{"testhook.txt"},
   646  			[]string{},
   647  			time.Now(),
   648  		)
   649  		assert.Nil(t, err)
   650  		assert.NotNil(t, response)
   651  		assert.Equal(t, 1, len(response.FileInfos))
   652  		fileId := response.FileInfos[0].Id
   653  
   654  		fileInfo, err := th.App.GetFileInfo(fileId)
   655  		assert.Nil(t, err)
   656  		assert.NotNil(t, fileInfo)
   657  		assert.Equal(t, "modifiedinfo", fileInfo.Name)
   658  
   659  		fileReader, err := th.App.FileReader(fileInfo.Path)
   660  		assert.Nil(t, err)
   661  		var resultBuf bytes.Buffer
   662  		io.Copy(&resultBuf, fileReader)
   663  		assert.Equal(t, "changedtext", resultBuf.String())
   664  	})
   665  }
   666  
   667  func TestUserWillLogIn_Blocked(t *testing.T) {
   668  	th := Setup(t).InitBasic()
   669  	defer th.TearDown()
   670  
   671  	err := th.App.UpdatePassword(th.BasicUser, "hunter2")
   672  	assert.Nil(t, err, "Error updating user password: %s", err)
   673  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   674  		[]string{
   675  			`
   676  		package main
   677  
   678  		import (
   679  			"github.com/mattermost/mattermost-server/v5/plugin"
   680  			"github.com/mattermost/mattermost-server/v5/model"
   681  		)
   682  
   683  		type MyPlugin struct {
   684  			plugin.MattermostPlugin
   685  		}
   686  
   687  		func (p *MyPlugin) UserWillLogIn(c *plugin.Context, user *model.User) string {
   688  			return "Blocked By Plugin"
   689  		}
   690  
   691  		func main() {
   692  			plugin.ClientMain(&MyPlugin{})
   693  		}
   694  	`}, th.App, th.App.NewPluginAPI)
   695  	defer tearDown()
   696  
   697  	r := &http.Request{}
   698  	w := httptest.NewRecorder()
   699  	err = th.App.DoLogin(w, r, th.BasicUser, "", false, false, false)
   700  
   701  	assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
   702  }
   703  
   704  func TestUserWillLogInIn_Passed(t *testing.T) {
   705  	th := Setup(t).InitBasic()
   706  	defer th.TearDown()
   707  
   708  	err := th.App.UpdatePassword(th.BasicUser, "hunter2")
   709  
   710  	assert.Nil(t, err, "Error updating user password: %s", err)
   711  
   712  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   713  		[]string{
   714  			`
   715  		package main
   716  
   717  		import (
   718  			"github.com/mattermost/mattermost-server/v5/plugin"
   719  			"github.com/mattermost/mattermost-server/v5/model"
   720  		)
   721  
   722  		type MyPlugin struct {
   723  			plugin.MattermostPlugin
   724  		}
   725  
   726  		func (p *MyPlugin) UserWillLogIn(c *plugin.Context, user *model.User) string {
   727  			return ""
   728  		}
   729  
   730  		func main() {
   731  			plugin.ClientMain(&MyPlugin{})
   732  		}
   733  	`}, th.App, th.App.NewPluginAPI)
   734  	defer tearDown()
   735  
   736  	r := &http.Request{}
   737  	w := httptest.NewRecorder()
   738  	err = th.App.DoLogin(w, r, th.BasicUser, "", false, false, false)
   739  
   740  	assert.Nil(t, err, "Expected nil, got %s", err)
   741  	assert.Equal(t, th.App.Session().UserId, th.BasicUser.Id)
   742  }
   743  
   744  func TestUserHasLoggedIn(t *testing.T) {
   745  	th := Setup(t).InitBasic()
   746  	defer th.TearDown()
   747  
   748  	err := th.App.UpdatePassword(th.BasicUser, "hunter2")
   749  
   750  	assert.Nil(t, err, "Error updating user password: %s", err)
   751  
   752  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   753  		[]string{
   754  			`
   755  		package main
   756  
   757  		import (
   758  			"github.com/mattermost/mattermost-server/v5/plugin"
   759  			"github.com/mattermost/mattermost-server/v5/model"
   760  		)
   761  
   762  		type MyPlugin struct {
   763  			plugin.MattermostPlugin
   764  		}
   765  
   766  		func (p *MyPlugin) UserHasLoggedIn(c *plugin.Context, user *model.User) {
   767  			user.FirstName = "plugin-callback-success"
   768  			p.API.UpdateUser(user)
   769  		}
   770  
   771  		func main() {
   772  			plugin.ClientMain(&MyPlugin{})
   773  		}
   774  	`}, th.App, th.App.NewPluginAPI)
   775  	defer tearDown()
   776  
   777  	r := &http.Request{}
   778  	w := httptest.NewRecorder()
   779  	err = th.App.DoLogin(w, r, th.BasicUser, "", false, false, false)
   780  
   781  	assert.Nil(t, err, "Expected nil, got %s", err)
   782  
   783  	time.Sleep(2 * time.Second)
   784  
   785  	user, _ := th.App.GetUser(th.BasicUser.Id)
   786  
   787  	assert.Equal(t, user.FirstName, "plugin-callback-success", "Expected firstname overwrite, got default")
   788  }
   789  
   790  func TestUserHasBeenCreated(t *testing.T) {
   791  	th := Setup(t)
   792  	defer th.TearDown()
   793  
   794  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   795  		[]string{
   796  			`
   797  		package main
   798  
   799  		import (
   800  			"github.com/mattermost/mattermost-server/v5/plugin"
   801  			"github.com/mattermost/mattermost-server/v5/model"
   802  		)
   803  
   804  		type MyPlugin struct {
   805  			plugin.MattermostPlugin
   806  		}
   807  
   808  		func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
   809  			user.Nickname = "plugin-callback-success"
   810  			p.API.UpdateUser(user)
   811  		}
   812  
   813  		func main() {
   814  			plugin.ClientMain(&MyPlugin{})
   815  		}
   816  	`}, th.App, th.App.NewPluginAPI)
   817  	defer tearDown()
   818  
   819  	user := &model.User{
   820  		Email:       model.NewId() + "success+test@example.com",
   821  		Nickname:    "Darth Vader",
   822  		Username:    "vader" + model.NewId(),
   823  		Password:    "passwd1",
   824  		AuthService: "",
   825  	}
   826  	_, err := th.App.CreateUser(user)
   827  	require.Nil(t, err)
   828  
   829  	time.Sleep(1 * time.Second)
   830  
   831  	user, err = th.App.GetUser(user.Id)
   832  	require.Nil(t, err)
   833  	require.Equal(t, "plugin-callback-success", user.Nickname)
   834  }
   835  
   836  func TestErrorString(t *testing.T) {
   837  	th := Setup(t)
   838  	defer th.TearDown()
   839  
   840  	t.Run("errors.New", func(t *testing.T) {
   841  		tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t,
   842  			[]string{
   843  				`
   844  			package main
   845  
   846  			import (
   847  				"errors"
   848  
   849  				"github.com/mattermost/mattermost-server/v5/plugin"
   850  			)
   851  
   852  			type MyPlugin struct {
   853  				plugin.MattermostPlugin
   854  			}
   855  
   856  			func (p *MyPlugin) OnActivate() error {
   857  				return errors.New("simulate failure")
   858  			}
   859  
   860  			func main() {
   861  				plugin.ClientMain(&MyPlugin{})
   862  			}
   863  		`}, th.App, th.App.NewPluginAPI)
   864  		defer tearDown()
   865  
   866  		require.Len(t, activationErrors, 1)
   867  		require.NotNil(t, activationErrors[0])
   868  		require.Contains(t, activationErrors[0].Error(), "simulate failure")
   869  	})
   870  
   871  	t.Run("AppError", func(t *testing.T) {
   872  		tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t,
   873  			[]string{
   874  				`
   875  			package main
   876  
   877  			import (
   878  				"github.com/mattermost/mattermost-server/v5/plugin"
   879  				"github.com/mattermost/mattermost-server/v5/model"
   880  			)
   881  
   882  			type MyPlugin struct {
   883  				plugin.MattermostPlugin
   884  			}
   885  
   886  			func (p *MyPlugin) OnActivate() error {
   887  				return model.NewAppError("where", "id", map[string]interface{}{"param": 1}, "details", 42)
   888  			}
   889  
   890  			func main() {
   891  				plugin.ClientMain(&MyPlugin{})
   892  			}
   893  		`}, th.App, th.App.NewPluginAPI)
   894  		defer tearDown()
   895  
   896  		require.Len(t, activationErrors, 1)
   897  		require.NotNil(t, activationErrors[0])
   898  
   899  		cause := errors.Cause(activationErrors[0])
   900  		require.IsType(t, &model.AppError{}, cause)
   901  
   902  		// params not expected, since not exported
   903  		expectedErr := model.NewAppError("where", "id", nil, "details", 42)
   904  		require.Equal(t, expectedErr, cause)
   905  	})
   906  }
   907  
   908  func TestHookContext(t *testing.T) {
   909  	th := Setup(t).InitBasic()
   910  	defer th.TearDown()
   911  
   912  	// We don't actually have a session, we are faking it so just set something arbitrarily
   913  	th.App.Session().Id = model.NewId()
   914  	th.App.requestId = model.NewId()
   915  	th.App.ipAddress = model.NewId()
   916  	th.App.acceptLanguage = model.NewId()
   917  	th.App.userAgent = model.NewId()
   918  
   919  	var mockAPI plugintest.API
   920  	mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
   921  	mockAPI.On("LogDebug", th.App.Session().Id).Return(nil)
   922  	mockAPI.On("LogInfo", th.App.RequestId()).Return(nil)
   923  	mockAPI.On("LogError", th.App.IpAddress()).Return(nil)
   924  	mockAPI.On("LogWarn", th.App.AcceptLanguage()).Return(nil)
   925  	mockAPI.On("DeleteTeam", th.App.UserAgent()).Return(nil)
   926  
   927  	tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
   928  		[]string{
   929  			`
   930  		package main
   931  
   932  		import (
   933  			"github.com/mattermost/mattermost-server/v5/plugin"
   934  			"github.com/mattermost/mattermost-server/v5/model"
   935  		)
   936  
   937  		type MyPlugin struct {
   938  			plugin.MattermostPlugin
   939  		}
   940  
   941  		func (p *MyPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
   942  			p.API.LogDebug(c.SessionId)
   943  			p.API.LogInfo(c.RequestId)
   944  			p.API.LogError(c.IpAddress)
   945  			p.API.LogWarn(c.AcceptLanguage)
   946  			p.API.DeleteTeam(c.UserAgent)
   947  		}
   948  
   949  		func main() {
   950  			plugin.ClientMain(&MyPlugin{})
   951  		}
   952  	`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
   953  	defer tearDown()
   954  
   955  	post := &model.Post{
   956  		UserId:    th.BasicUser.Id,
   957  		ChannelId: th.BasicChannel.Id,
   958  		Message:   "not this",
   959  		CreateAt:  model.GetMillis() - 10000,
   960  	}
   961  	_, err := th.App.CreatePost(post, th.BasicChannel, false, true)
   962  	require.Nil(t, err)
   963  }
   964  
   965  func TestActiveHooks(t *testing.T) {
   966  	th := Setup(t)
   967  	defer th.TearDown()
   968  
   969  	t.Run("", func(t *testing.T) {
   970  		tearDown, pluginIds, _ := SetAppEnvironmentWithPlugins(t,
   971  			[]string{
   972  				`
   973  			package main
   974  
   975  			import (
   976  				"github.com/mattermost/mattermost-server/v5/model"
   977  				"github.com/mattermost/mattermost-server/v5/plugin"
   978  			)
   979  
   980  			type MyPlugin struct {
   981  				plugin.MattermostPlugin
   982  			}
   983  
   984  			func (p *MyPlugin) OnActivate() error {
   985  				return nil
   986  			}
   987  
   988  			func (p *MyPlugin) OnConfigurationChange() error {
   989  				return nil
   990  			}
   991  
   992  			func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
   993  				user.Nickname = "plugin-callback-success"
   994  				p.API.UpdateUser(user)
   995  			}
   996  
   997  			func main() {
   998  				plugin.ClientMain(&MyPlugin{})
   999  			}
  1000  		`}, th.App, th.App.NewPluginAPI)
  1001  		defer tearDown()
  1002  
  1003  		require.Len(t, pluginIds, 1)
  1004  		pluginId := pluginIds[0]
  1005  
  1006  		require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginId))
  1007  		user1 := &model.User{
  1008  			Email:       model.NewId() + "success+test@example.com",
  1009  			Nickname:    "Darth Vader1",
  1010  			Username:    "vader" + model.NewId(),
  1011  			Password:    "passwd1",
  1012  			AuthService: "",
  1013  		}
  1014  		_, appErr := th.App.CreateUser(user1)
  1015  		require.Nil(t, appErr)
  1016  		time.Sleep(1 * time.Second)
  1017  		user1, appErr = th.App.GetUser(user1.Id)
  1018  		require.Nil(t, appErr)
  1019  		require.Equal(t, "plugin-callback-success", user1.Nickname)
  1020  
  1021  		// Disable plugin
  1022  		require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginId))
  1023  		require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginId))
  1024  
  1025  		hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginId)
  1026  		require.Error(t, err)
  1027  		require.Nil(t, hooks)
  1028  
  1029  		// Should fail to find pluginId as it was deleted when deactivated
  1030  		path, err := th.App.GetPluginsEnvironment().PublicFilesPath(pluginId)
  1031  		require.Error(t, err)
  1032  		require.Empty(t, path)
  1033  	})
  1034  }
  1035  
  1036  func TestHookMetrics(t *testing.T) {
  1037  	th := Setup(t)
  1038  	defer th.TearDown()
  1039  
  1040  	t.Run("", func(t *testing.T) {
  1041  		metricsMock := &mocks.MetricsInterface{}
  1042  
  1043  		pluginDir, err := ioutil.TempDir("", "")
  1044  		require.NoError(t, err)
  1045  		webappPluginDir, err := ioutil.TempDir("", "")
  1046  		require.NoError(t, err)
  1047  		defer os.RemoveAll(pluginDir)
  1048  		defer os.RemoveAll(webappPluginDir)
  1049  
  1050  		env, err := plugin.NewEnvironment(th.App.NewPluginAPI, pluginDir, webappPluginDir, th.App.Log(), metricsMock)
  1051  		require.NoError(t, err)
  1052  
  1053  		th.App.SetPluginsEnvironment(env)
  1054  
  1055  		pluginId := model.NewId()
  1056  		backend := filepath.Join(pluginDir, pluginId, "backend.exe")
  1057  		code :=
  1058  			`
  1059  	package main
  1060  
  1061  	import (
  1062  		"github.com/mattermost/mattermost-server/v5/model"
  1063  		"github.com/mattermost/mattermost-server/v5/plugin"
  1064  	)
  1065  
  1066  	type MyPlugin struct {
  1067  		plugin.MattermostPlugin
  1068  	}
  1069  
  1070  	func (p *MyPlugin) OnActivate() error {
  1071  		return nil
  1072  	}
  1073  
  1074  	func (p *MyPlugin) OnConfigurationChange() error {
  1075  		return nil
  1076  	}
  1077  
  1078  	func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
  1079  		user.Nickname = "plugin-callback-success"
  1080  		p.API.UpdateUser(user)
  1081  	}
  1082  
  1083  	func main() {
  1084  		plugin.ClientMain(&MyPlugin{})
  1085  	}
  1086  `
  1087  		utils.CompileGo(t, code, backend)
  1088  		ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(`{"id": "`+pluginId+`", "backend": {"executable": "backend.exe"}}`), 0600)
  1089  
  1090  		// Setup mocks before activating
  1091  		metricsMock.On("ObservePluginHookDuration", pluginId, "Implemented", true, mock.Anything).Return()
  1092  		metricsMock.On("ObservePluginHookDuration", pluginId, "OnActivate", true, mock.Anything).Return()
  1093  		metricsMock.On("ObservePluginHookDuration", pluginId, "OnDeactivate", true, mock.Anything).Return()
  1094  		metricsMock.On("ObservePluginHookDuration", pluginId, "OnConfigurationChange", true, mock.Anything).Return()
  1095  		metricsMock.On("ObservePluginHookDuration", pluginId, "UserHasBeenCreated", true, mock.Anything).Return()
  1096  
  1097  		// Don't care about these calls.
  1098  		metricsMock.On("ObservePluginApiDuration", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
  1099  		metricsMock.On("ObservePluginMultiHookIterationDuration", mock.Anything, mock.Anything, mock.Anything).Return()
  1100  		metricsMock.On("ObservePluginMultiHookDuration", mock.Anything).Return()
  1101  
  1102  		_, _, activationErr := env.Activate(pluginId)
  1103  		require.NoError(t, activationErr)
  1104  
  1105  		th.App.UpdateConfig(func(cfg *model.Config) {
  1106  			cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{
  1107  				Enable: true,
  1108  			}
  1109  		})
  1110  
  1111  		require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginId))
  1112  
  1113  		user1 := &model.User{
  1114  			Email:       model.NewId() + "success+test@example.com",
  1115  			Nickname:    "Darth Vader1",
  1116  			Username:    "vader" + model.NewId(),
  1117  			Password:    "passwd1",
  1118  			AuthService: "",
  1119  		}
  1120  		_, appErr := th.App.CreateUser(user1)
  1121  		require.Nil(t, appErr)
  1122  		time.Sleep(1 * time.Second)
  1123  		user1, appErr = th.App.GetUser(user1.Id)
  1124  		require.Nil(t, appErr)
  1125  		require.Equal(t, "plugin-callback-success", user1.Nickname)
  1126  
  1127  		// Disable plugin
  1128  		require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginId))
  1129  		require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginId))
  1130  
  1131  		metricsMock.AssertExpectations(t)
  1132  	})
  1133  }