diff --git a/server/channels/app/plugin_api.go b/server/channels/app/plugin_api.go index 33ed501bbc..4e294cb082 100644 --- a/server/channels/app/plugin_api.go +++ b/server/channels/app/plugin_api.go @@ -993,6 +993,10 @@ func (api *PluginAPI) RolesGrantPermission(roleNames []string, permissionId stri return api.app.RolesGrantPermission(roleNames, permissionId) } +func (api *PluginAPI) UpdateUserRoles(userID string, newRoles string) (*model.User, *model.AppError) { + return api.app.UpdateUserRoles(api.ctx, userID, newRoles, true) +} + func (api *PluginAPI) LogDebug(msg string, keyValuePairs ...any) { api.logger.Debugw(msg, keyValuePairs...) } diff --git a/server/channels/app/plugin_api_tests/test_update_user_roles_plugin/main.go b/server/channels/app/plugin_api_tests/test_update_user_roles_plugin/main.go new file mode 100644 index 0000000000..1af24db101 --- /dev/null +++ b/server/channels/app/plugin_api_tests/test_update_user_roles_plugin/main.go @@ -0,0 +1,52 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package main + +import ( + "fmt" + + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/plugin" + "github.com/mattermost/mattermost/server/v8/channels/app/plugin_api_tests" +) + +type MyPlugin struct { + plugin.MattermostPlugin + configuration plugin_api_tests.BasicConfig +} + +func (p *MyPlugin) OnConfigurationChange() error { + if err := p.API.LoadPluginConfiguration(&p.configuration); err != nil { + return err + } + return nil +} + +func (p *MyPlugin) MessageWillBePosted(_ *plugin.Context, _ *model.Post) (*model.Post, string) { + if p.API.HasPermissionTo(p.configuration.BasicUserID, model.PermissionManageSystem) { + return nil, "basic user should not yet be a system admin" + } + + if _, appErr := p.API.UpdateUserRoles(p.configuration.BasicUserID, model.SystemAdminRoleId+" "+model.SystemUserRoleId); appErr != nil { + return nil, fmt.Sprintf("failed to update user roles: %s", appErr) + } + + if !p.API.HasPermissionTo(p.configuration.BasicUserID, model.PermissionManageSystem) { + return nil, "basic user should be a system admin" + } + + if _, appErr := p.API.UpdateUserRoles(p.configuration.BasicUserID, model.SystemUserRoleId); appErr != nil { + return nil, fmt.Sprintf("failed to update user roles: %s", appErr) + } + + if p.API.HasPermissionTo(p.configuration.BasicUserID, model.PermissionManageSystem) { + return nil, "basic user should no longer be a system admin" + } + + return nil, "OK" +} + +func main() { + plugin.ClientMain(&MyPlugin{}) +} diff --git a/server/public/plugin/api.go b/server/public/plugin/api.go index b90e84e08c..7631d529f9 100644 --- a/server/public/plugin/api.go +++ b/server/public/plugin/api.go @@ -1293,6 +1293,13 @@ type API interface { // @tag SharedChannels // Minimum server version: 9.5 UninviteRemoteFromChannel(channelID string, remoteID string) error + + // UpdateUserRoles updates the role for a user. + // + // @tag Team + // @tag User + // Minimum server version: 9.8 + UpdateUserRoles(userID, newRoles string) (*model.User, *model.AppError) } var handshake = plugin.HandshakeConfig{ diff --git a/server/public/plugin/api_timer_layer_generated.go b/server/public/plugin/api_timer_layer_generated.go index a680249f0b..fb569fc697 100644 --- a/server/public/plugin/api_timer_layer_generated.go +++ b/server/public/plugin/api_timer_layer_generated.go @@ -1364,3 +1364,10 @@ func (api *apiTimerLayer) UninviteRemoteFromChannel(channelID string, remoteID s api.recordTime(startTime, "UninviteRemoteFromChannel", _returnsA == nil) return _returnsA } + +func (api *apiTimerLayer) UpdateUserRoles(userID, newRoles string) (*model.User, *model.AppError) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.UpdateUserRoles(userID, newRoles) + api.recordTime(startTime, "UpdateUserRoles", _returnsB == nil) + return _returnsA, _returnsB +} diff --git a/server/public/plugin/client_rpc_generated.go b/server/public/plugin/client_rpc_generated.go index 084664d2fa..b5f84a2181 100644 --- a/server/public/plugin/client_rpc_generated.go +++ b/server/public/plugin/client_rpc_generated.go @@ -6535,3 +6535,33 @@ func (s *apiRPCServer) UninviteRemoteFromChannel(args *Z_UninviteRemoteFromChann } return nil } + +type Z_UpdateUserRolesArgs struct { + A string + B string +} + +type Z_UpdateUserRolesReturns struct { + A *model.User + B *model.AppError +} + +func (g *apiRPCClient) UpdateUserRoles(userID, newRoles string) (*model.User, *model.AppError) { + _args := &Z_UpdateUserRolesArgs{userID, newRoles} + _returns := &Z_UpdateUserRolesReturns{} + if err := g.client.Call("Plugin.UpdateUserRoles", _args, _returns); err != nil { + log.Printf("RPC call to UpdateUserRoles API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) UpdateUserRoles(args *Z_UpdateUserRolesArgs, returns *Z_UpdateUserRolesReturns) error { + if hook, ok := s.impl.(interface { + UpdateUserRoles(userID, newRoles string) (*model.User, *model.AppError) + }); ok { + returns.A, returns.B = hook.UpdateUserRoles(args.A, args.B) + } else { + return encodableError(fmt.Errorf("API UpdateUserRoles called but not implemented.")) + } + return nil +} diff --git a/server/public/plugin/plugintest/api.go b/server/public/plugin/plugintest/api.go index 75fa5ec6d5..805adefe8a 100644 --- a/server/public/plugin/plugintest/api.go +++ b/server/public/plugin/plugintest/api.go @@ -4355,6 +4355,34 @@ func (_m *API) UpdateUserCustomStatus(userID string, customStatus *model.CustomS return r0 } +// UpdateUserRoles provides a mock function with given fields: userID, newRoles +func (_m *API) UpdateUserRoles(userID string, newRoles string) (*model.User, *model.AppError) { + ret := _m.Called(userID, newRoles) + + var r0 *model.User + var r1 *model.AppError + if rf, ok := ret.Get(0).(func(string, string) (*model.User, *model.AppError)); ok { + return rf(userID, newRoles) + } + if rf, ok := ret.Get(0).(func(string, string) *model.User); ok { + r0 = rf(userID, newRoles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.User) + } + } + + if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok { + r1 = rf(userID, newRoles) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // UpdateUserStatus provides a mock function with given fields: userID, status func (_m *API) UpdateUserStatus(userID string, status string) (*model.Status, *model.AppError) { ret := _m.Called(userID, status) diff --git a/server/public/pluginapi/user.go b/server/public/pluginapi/user.go index bb6252dd6a..0b1d962b23 100644 --- a/server/public/pluginapi/user.go +++ b/server/public/pluginapi/user.go @@ -244,3 +244,12 @@ func (u *UserService) CreateAccessToken(userID, description string) (*model.User func (u *UserService) RevokeAccessToken(tokenID string) error { return normalizeAppErr(u.api.RevokeUserAccessToken(tokenID)) } + +// UpdateRoles updates the roles for a user. +// +// Minimum server version: 9.8 +func (u *UserService) UpdateRoles(userID, newRoles string) (*model.User, error) { + user, appErr := u.api.UpdateUserRoles(userID, newRoles) + + return user, normalizeAppErr(appErr) +}