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