From 099e6f74f302a48d39bb659fd008412d57bb7f69 Mon Sep 17 00:00:00 2001 From: kosgrz <45372453+kosgrz@users.noreply.github.com> Date: Tue, 29 Jan 2019 18:40:48 +0100 Subject: [PATCH] MM-13851 Added a UserWasCreated hook for server-side plugins (#10178) --- app/plugin_hooks_test.go | 47 +++++++++ app/user.go | 11 ++ plugin/client_rpc_generated.go | 177 ++++++++++++++++++++------------- plugin/hooks.go | 20 ++-- plugin/plugintest/hooks.go | 5 + 5 files changed, 182 insertions(+), 78 deletions(-) diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 72b0045839..20fa9115e5 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -816,6 +816,53 @@ func TestUserHasLoggedIn(t *testing.T) { } } +func TestUserHasBeenCreated(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + tearDown, _, _ := SetAppEnvironmentWithPlugins(t, + []string{ + ` + package main + + import ( + "github.com/mattermost/mattermost-server/plugin" + "github.com/mattermost/mattermost-server/model" + ) + + type MyPlugin struct { + plugin.MattermostPlugin + } + + func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) { + user.Nickname = "plugin-callback-success" + p.API.UpdateUser(user) + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + `}, th.App, th.App.NewPluginAPI) + defer tearDown() + + user := &model.User{ + Email: model.NewId() + "success+test@example.com", + Nickname: "Darth Vader", + Username: "vader" + model.NewId(), + Password: "passwd1", + AuthService: "", + } + _, err := th.App.CreateUser(user) + require.Nil(t, err) + + time.Sleep(1 * time.Second) + + user, err = th.App.GetUser(user.Id) + require.Nil(t, err) + + require.Equal(t, "plugin-callback-success", user.Nickname) +} + func TestErrorString(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/app/user.go b/app/user.go index 9e048dccc2..a48509d3c3 100644 --- a/app/user.go +++ b/app/user.go @@ -28,6 +28,7 @@ import ( "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/services/mfa" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils/fileutils" @@ -212,6 +213,16 @@ func (a *App) CreateUser(user *model.User) (*model.User, *model.AppError) { message.Add("user_id", ruser.Id) a.Publish(message) + if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { + a.Srv.Go(func() { + pluginContext := a.PluginContext() + pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { + hooks.UserHasBeenCreated(pluginContext, user) + return true + }, plugin.UserHasBeenCreatedId) + }) + } + return ruser, nil } diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index b88cb36ee3..339c1dac39 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -119,6 +119,112 @@ func (s *hooksRPCServer) ExecuteCommand(args *Z_ExecuteCommandArgs, returns *Z_E return nil } +func init() { + hookNameToId["UserHasBeenCreated"] = UserHasBeenCreatedId +} + +type Z_UserHasBeenCreatedArgs struct { + A *Context + B *model.User +} + +type Z_UserHasBeenCreatedReturns struct { +} + +func (g *hooksRPCClient) UserHasBeenCreated(c *Context, user *model.User) { + _args := &Z_UserHasBeenCreatedArgs{c, user} + _returns := &Z_UserHasBeenCreatedReturns{} + if g.implemented[UserHasBeenCreatedId] { + if err := g.client.Call("Plugin.UserHasBeenCreated", _args, _returns); err != nil { + g.log.Error("RPC call UserHasBeenCreated to plugin failed.", mlog.Err(err)) + } + } + +} + +func (s *hooksRPCServer) UserHasBeenCreated(args *Z_UserHasBeenCreatedArgs, returns *Z_UserHasBeenCreatedReturns) error { + if hook, ok := s.impl.(interface { + UserHasBeenCreated(c *Context, user *model.User) + }); ok { + hook.UserHasBeenCreated(args.A, args.B) + + } else { + return encodableError(fmt.Errorf("Hook UserHasBeenCreated called but not implemented.")) + } + return nil +} + +func init() { + hookNameToId["UserWillLogIn"] = UserWillLogInId +} + +type Z_UserWillLogInArgs struct { + A *Context + B *model.User +} + +type Z_UserWillLogInReturns struct { + A string +} + +func (g *hooksRPCClient) UserWillLogIn(c *Context, user *model.User) string { + _args := &Z_UserWillLogInArgs{c, user} + _returns := &Z_UserWillLogInReturns{} + if g.implemented[UserWillLogInId] { + if err := g.client.Call("Plugin.UserWillLogIn", _args, _returns); err != nil { + g.log.Error("RPC call UserWillLogIn to plugin failed.", mlog.Err(err)) + } + } + return _returns.A +} + +func (s *hooksRPCServer) UserWillLogIn(args *Z_UserWillLogInArgs, returns *Z_UserWillLogInReturns) error { + if hook, ok := s.impl.(interface { + UserWillLogIn(c *Context, user *model.User) string + }); ok { + returns.A = hook.UserWillLogIn(args.A, args.B) + + } else { + return encodableError(fmt.Errorf("Hook UserWillLogIn called but not implemented.")) + } + return nil +} + +func init() { + hookNameToId["UserHasLoggedIn"] = UserHasLoggedInId +} + +type Z_UserHasLoggedInArgs struct { + A *Context + B *model.User +} + +type Z_UserHasLoggedInReturns struct { +} + +func (g *hooksRPCClient) UserHasLoggedIn(c *Context, user *model.User) { + _args := &Z_UserHasLoggedInArgs{c, user} + _returns := &Z_UserHasLoggedInReturns{} + if g.implemented[UserHasLoggedInId] { + if err := g.client.Call("Plugin.UserHasLoggedIn", _args, _returns); err != nil { + g.log.Error("RPC call UserHasLoggedIn to plugin failed.", mlog.Err(err)) + } + } + +} + +func (s *hooksRPCServer) UserHasLoggedIn(args *Z_UserHasLoggedInArgs, returns *Z_UserHasLoggedInReturns) error { + if hook, ok := s.impl.(interface { + UserHasLoggedIn(c *Context, user *model.User) + }); ok { + hook.UserHasLoggedIn(args.A, args.B) + + } else { + return encodableError(fmt.Errorf("Hook UserHasLoggedIn called but not implemented.")) + } + return nil +} + func init() { hookNameToId["MessageHasBeenPosted"] = MessageHasBeenPostedId } @@ -369,77 +475,6 @@ func (s *hooksRPCServer) UserHasLeftTeam(args *Z_UserHasLeftTeamArgs, returns *Z return nil } -func init() { - hookNameToId["UserWillLogIn"] = UserWillLogInId -} - -type Z_UserWillLogInArgs struct { - A *Context - B *model.User -} - -type Z_UserWillLogInReturns struct { - A string -} - -func (g *hooksRPCClient) UserWillLogIn(c *Context, user *model.User) string { - _args := &Z_UserWillLogInArgs{c, user} - _returns := &Z_UserWillLogInReturns{} - if g.implemented[UserWillLogInId] { - if err := g.client.Call("Plugin.UserWillLogIn", _args, _returns); err != nil { - g.log.Error("RPC call UserWillLogIn to plugin failed.", mlog.Err(err)) - } - } - return _returns.A -} - -func (s *hooksRPCServer) UserWillLogIn(args *Z_UserWillLogInArgs, returns *Z_UserWillLogInReturns) error { - if hook, ok := s.impl.(interface { - UserWillLogIn(c *Context, user *model.User) string - }); ok { - returns.A = hook.UserWillLogIn(args.A, args.B) - - } else { - return encodableError(fmt.Errorf("Hook UserWillLogIn called but not implemented.")) - } - return nil -} - -func init() { - hookNameToId["UserHasLoggedIn"] = UserHasLoggedInId -} - -type Z_UserHasLoggedInArgs struct { - A *Context - B *model.User -} - -type Z_UserHasLoggedInReturns struct { -} - -func (g *hooksRPCClient) UserHasLoggedIn(c *Context, user *model.User) { - _args := &Z_UserHasLoggedInArgs{c, user} - _returns := &Z_UserHasLoggedInReturns{} - if g.implemented[UserHasLoggedInId] { - if err := g.client.Call("Plugin.UserHasLoggedIn", _args, _returns); err != nil { - g.log.Error("RPC call UserHasLoggedIn to plugin failed.", mlog.Err(err)) - } - } - -} - -func (s *hooksRPCServer) UserHasLoggedIn(args *Z_UserHasLoggedInArgs, returns *Z_UserHasLoggedInReturns) error { - if hook, ok := s.impl.(interface { - UserHasLoggedIn(c *Context, user *model.User) - }); ok { - hook.UserHasLoggedIn(args.A, args.B) - - } else { - return encodableError(fmt.Errorf("Hook UserHasLoggedIn called but not implemented.")) - } - return nil -} - type Z_RegisterCommandArgs struct { A *model.Command } diff --git a/plugin/hooks.go b/plugin/hooks.go index b5cff6f6b8..03f7484d77 100644 --- a/plugin/hooks.go +++ b/plugin/hooks.go @@ -32,6 +32,7 @@ const ( FileWillBeUploadedId = 14 UserWillLogInId = 15 UserHasLoggedInId = 16 + UserHasBeenCreatedId = 17 TotalHooksId = iota ) @@ -71,6 +72,18 @@ type Hooks interface { // API. ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) + // UserHasBeenCreated is invoked after a user was created. + // + // Minimum server version: 5.10 + UserHasBeenCreated(c *Context, user *model.User) + + // UserWillLogIn before the login of the user is returned. Returning a non empty string will reject the login event. + // If you don't need to reject the login event, see UserHasLoggedIn + UserWillLogIn(c *Context, user *model.User) string + + // UserHasLoggedIn is invoked after a user has logged in. + UserHasLoggedIn(c *Context, user *model.User) + // MessageWillBePosted is invoked when a message is posted by a user before it is committed // to the database. If you also want to act on edited posts, see MessageWillBeUpdated. // @@ -126,13 +139,6 @@ type Hooks interface { // If actor is not nil, the user was removed from the team by the actor. UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User) - // UserWillLogIn before the login of the user is returned. Returning a non empty string will reject the login event. - // If you don't need to reject the login event, see UserHasLoggedIn - UserWillLogIn(c *Context, user *model.User) string - - // UserHasLoggedIn is invoked after a user has logged in. - UserHasLoggedIn(c *Context, user *model.User) - // FileWillBeUploaded is invoked when a file is uploaded, but before it is committed to backing store. // Read from file to retrieve the body of the uploaded file. // diff --git a/plugin/plugintest/hooks.go b/plugin/plugintest/hooks.go index 838cccc020..5ebeecc42e 100644 --- a/plugin/plugintest/hooks.go +++ b/plugin/plugintest/hooks.go @@ -194,6 +194,11 @@ func (_m *Hooks) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req _m.Called(c, w, r) } +// UserHasBeenCreated provides a mock function with given fields: c, user +func (_m *Hooks) UserHasBeenCreated(c *plugin.Context, user *model.User) { + _m.Called(c, user) +} + // UserHasJoinedChannel provides a mock function with given fields: c, channelMember, actor func (_m *Hooks) UserHasJoinedChannel(c *plugin.Context, channelMember *model.ChannelMember, actor *model.User) { _m.Called(c, channelMember, actor)