Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the remoteID migration, and making the synthetic user identification more robust #539

Merged
merged 13 commits into from
Mar 12, 2024
4 changes: 2 additions & 2 deletions server/ce2e/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,11 @@ func TestSelectiveSync(t *testing.T) {

conn, err := mattermost.PostgresConnection(context.Background())
require.NoError(t, err)
defer conn.Close()

// Mark user as synthetic
_, err = conn.Exec("UPDATE Users SET RemoteId = 'syntetic-user' WHERE Id = $1", synthetic.Id)
_, err = conn.Exec("UPDATE Users SET RemoteId = (SELECT remoteId FROM remoteclusters) WHERE Username = 'msteams_synthetic'")
jespino marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(t, err)
defer conn.Close()

team, _, err := adminClient.GetTeamByName(context.Background(), "test", "")
require.NoError(t, err)
Expand Down
10 changes: 5 additions & 5 deletions server/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,7 @@ func TestExecutePromoteCommand(t *testing.T) {
params: []string{"existing-user", "existing-user"},
setupAPI: func(api *plugintest.API) {
api.On("HasPermissionTo", testutils.GetUserID(), model.PermissionManageSystem).Return(true).Times(1)
api.On("GetUserByUsername", "existing-user").Return(&model.User{Id: "test", Username: "existing-user", RemoteId: model.NewString("test")}, nil).Once()
api.On("GetUserByUsername", "existing-user").Return(&model.User{Id: "test", Username: "existing-user", RemoteId: model.NewString("remote-id")}, nil).Once()
api.On("SendEphemeralPost", testutils.GetUserID(), testutils.GetEphemeralPost(p.userID, testutils.GetChannelID(), "Error: Unable to promote account existing-user, it is not a known msteams user account")).Return(testutils.GetPost(testutils.GetChannelID(), testutils.GetUserID(), time.Now().UnixMicro())).Once()
},
setupStore: func(s *mockStore.Store) {
Expand All @@ -1392,7 +1392,7 @@ func TestExecutePromoteCommand(t *testing.T) {
params: []string{"valid-user", "new-user"},
setupAPI: func(api *plugintest.API) {
api.On("HasPermissionTo", testutils.GetUserID(), model.PermissionManageSystem).Return(true).Times(1)
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("test")}, nil).Once()
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("remote-id")}, nil).Once()
api.On("GetUserByUsername", "new-user").Return(&model.User{Id: "test2", Username: "new-user", RemoteId: nil}, nil).Once()
api.On("SendEphemeralPost", testutils.GetUserID(), testutils.GetEphemeralPost(p.userID, testutils.GetChannelID(), "Error: the promoted username already exists, please use a different username.")).Return(testutils.GetPost(testutils.GetChannelID(), testutils.GetUserID(), time.Now().UnixMicro())).Once()
},
Expand All @@ -1405,7 +1405,7 @@ func TestExecutePromoteCommand(t *testing.T) {
params: []string{"valid-user", "new-user"},
setupAPI: func(api *plugintest.API) {
api.On("HasPermissionTo", testutils.GetUserID(), model.PermissionManageSystem).Return(true).Times(1)
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("test")}, nil).Once()
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("remote-id")}, nil).Once()
api.On("GetUserByUsername", "new-user").Return(nil, &model.AppError{}).Once()
api.On("UpdateUser", mock.Anything).Return(nil, &model.AppError{}).Once()
api.On("SendEphemeralPost", testutils.GetUserID(), testutils.GetEphemeralPost(p.userID, testutils.GetChannelID(), "Error: Unable to promote account valid-user")).Return(testutils.GetPost(testutils.GetChannelID(), testutils.GetUserID(), time.Now().UnixMicro())).Once()
Expand All @@ -1419,7 +1419,7 @@ func TestExecutePromoteCommand(t *testing.T) {
params: []string{"valid-user", "new-user"},
setupAPI: func(api *plugintest.API) {
api.On("HasPermissionTo", testutils.GetUserID(), model.PermissionManageSystem).Return(true).Times(1)
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("test")}, nil).Once()
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("remote-id")}, nil).Once()
api.On("GetUserByUsername", "new-user").Return(nil, &model.AppError{}).Once()
api.On("UpdateUser", &model.User{Id: "test", Username: "new-user", RemoteId: nil}).Return(&model.User{Id: "test", Username: "new-user", RemoteId: nil}, nil).Once()
api.On("SendEphemeralPost", testutils.GetUserID(), testutils.GetEphemeralPost(p.userID, testutils.GetChannelID(), "Account valid-user has been promoted and updated the username to new-user")).Return(testutils.GetPost(testutils.GetChannelID(), testutils.GetUserID(), time.Now().UnixMicro())).Once()
Expand All @@ -1433,7 +1433,7 @@ func TestExecutePromoteCommand(t *testing.T) {
params: []string{"valid-user", "valid-user"},
setupAPI: func(api *plugintest.API) {
api.On("HasPermissionTo", testutils.GetUserID(), model.PermissionManageSystem).Return(true).Times(1)
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("test")}, nil).Times(2)
api.On("GetUserByUsername", "valid-user").Return(&model.User{Id: "test", Username: "valid-user", RemoteId: model.NewString("remote-id")}, nil).Times(2)
api.On("UpdateUser", &model.User{Id: "test", Username: "valid-user", RemoteId: nil}).Return(&model.User{Id: "test", Username: "valid-user", RemoteId: nil}, nil).Times(1)
api.On("SendEphemeralPost", testutils.GetUserID(), testutils.GetEphemeralPost(p.userID, testutils.GetChannelID(), "Account valid-user has been promoted and updated the username to valid-user")).Return(testutils.GetPost(testutils.GetChannelID(), testutils.GetUserID(), time.Now().UnixMicro())).Once()
},
Expand Down
1 change: 1 addition & 0 deletions server/handlers/getters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func (pm *pluginMock) GetMaxSizeForCompleteDownload() int { return
func (pm *pluginMock) GetBufferSizeForStreaming() int { return pm.bufferSizeForStreaming }
func (pm *pluginMock) GetBotUserID() string { return pm.botUserID }
func (pm *pluginMock) GetURL() string { return pm.url }
func (pm *pluginMock) IsRemoteUser(user *model.User) bool { return user.RemoteId != nil }
func (pm *pluginMock) GetMetrics() metrics.Metrics { return pm.metrics }
func (pm *pluginMock) GetClientForApp() msteams.Client { return pm.appClient }
func (pm *pluginMock) GetClientForUser(string) (msteams.Client, error) { return pm.userClient, nil }
Expand Down
3 changes: 2 additions & 1 deletion server/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type PluginIface interface {
GenerateRandomPassword() string
ChatSpansPlatforms(channelID string) (bool, *model.AppError)
GetSelectiveSync() bool
IsRemoteUser(user *model.User) bool
}

type ActivityHandler struct {
Expand Down Expand Up @@ -596,7 +597,7 @@ func (ah *ActivityHandler) isRemoteUser(userID string) bool {
return false
}

return user.RemoteId != nil && *user.RemoteId != "" && strings.HasPrefix(user.Username, "msteams_")
return ah.plugin.IsRemoteUser(user)
}

func IsDirectMessage(chatID string) bool {
Expand Down
14 changes: 14 additions & 0 deletions server/handlers/mocks/PluginIface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/message_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

func (p *Plugin) UserWillLogIn(_ *plugin.Context, user *model.User) string {
if user.RemoteId != nil && *user.RemoteId != "" && p.getConfiguration().AutomaticallyPromoteSyntheticUsers {
if p.IsRemoteUser(user) && p.getConfiguration().AutomaticallyPromoteSyntheticUsers {
*user.RemoteId = ""
if _, appErr := p.API.UpdateUser(user); appErr != nil {
p.API.LogWarn("Unable to promote synthetic user", "user_id", user.Id, "error", appErr.Error())
Expand Down
58 changes: 26 additions & 32 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base32"
"encoding/base64"
"encoding/pem"
"fmt"
Expand All @@ -19,7 +18,6 @@ import (
"time"

"github.com/gosimple/slug"
"github.com/pborman/uuid"
"github.com/pkg/errors"
"golang.org/x/oauth2"

Expand Down Expand Up @@ -484,6 +482,24 @@ func (p *Plugin) onActivate() error {
return err
}

p.remoteID, err = p.API.RegisterPluginForSharedChannels(model.RegisterPluginOpts{
Displayname: pluginID,
PluginID: pluginID,
CreatorID: p.userID,
AutoShareDMs: true,
AutoInvited: true,
})
if err != nil {
return err
}
p.API.LogInfo("Registered plugin for shared channels", "remote_id", p.remoteID)

if p.getConfiguration().DisableSyncMsg {
if err = p.API.UnregisterPluginForSharedChannels(pluginID); err != nil {
jespino marked this conversation as resolved.
Show resolved Hide resolved
p.API.LogWarn("Unable to unregister plugin for shared channels", "error", err)
}
}

if p.store == nil {
if p.apiClient.Store.DriverName() != model.DatabaseDriverPostgres {
return fmt.Errorf("unsupported database driver: %s", p.apiClient.Store.DriverName())
Expand All @@ -502,26 +518,12 @@ func (p *Plugin) onActivate() error {
)
p.store = timerlayer.New(store, p.GetMetrics())

if err = p.store.Init(); err != nil {
if err = p.store.Init(p.remoteID); err != nil {
return err
}
}

if !p.getConfiguration().DisableSyncMsg {
remoteID, err := p.API.RegisterPluginForSharedChannels(model.RegisterPluginOpts{
Displayname: pluginID,
PluginID: pluginID,
CreatorID: p.userID,
AutoShareDMs: true,
AutoInvited: true,
})
if err != nil {
return err
}
p.remoteID = remoteID

p.API.LogInfo("Registered plugin for shared channels", "remote_id", p.remoteID)

linkedChannels, err := p.store.ListChannelLinks()
if err != nil {
p.API.LogError("Failed to list channel links for shared channels", "error", err.Error())
Expand All @@ -543,10 +545,6 @@ func (p *Plugin) onActivate() error {

p.API.LogInfo("Shared previously linked channel", "channel_id", linkedChannel.MattermostChannelID)
}
} else {
if err := p.API.UnregisterPluginForSharedChannels(pluginID); err != nil {
p.API.LogWarn("Unable to unregister plugin for shared channels", "error", err)
}
}

p.apiHandler = NewAPI(p, p.store)
Expand Down Expand Up @@ -662,7 +660,7 @@ func (p *Plugin) syncUsers() {
}
}

if isRemoteUser(mmUser) {
if p.IsRemoteUser(mmUser) {
if msUser.IsAccountEnabled {
// Activate the deactivated Mattermost user corresponding to the MS Teams user.
if mmUser.DeleteAt != 0 {
Expand Down Expand Up @@ -704,7 +702,7 @@ func (p *Plugin) syncUsers() {
if msUser.Type == msteamsUserTypeGuest {
// Check if syncing of MS Teams guest users is disabled.
if !syncGuestUsers {
if isUserPresent && isRemoteUser(mmUser) && mmUser.DeleteAt == 0 {
if isUserPresent && p.IsRemoteUser(mmUser) && mmUser.DeleteAt == 0 {
// Deactivate the Mattermost user corresponding to the MS Teams guest user.
p.API.LogInfo("Deactivating the guest user account", "user_id", mmUser.Id, "teams_user_id", msUser.ID)
if err := p.API.UpdateUserActive(mmUser.Id, false); err != nil {
Expand All @@ -718,13 +716,9 @@ func (p *Plugin) syncUsers() {

username := "msteams_" + slug.Make(msUser.DisplayName)
if !isUserPresent {
userUUID := uuid.Parse(msUser.ID)
encoding := base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769").WithPadding(base32.NoPadding)
shortUserID := encoding.EncodeToString(userUUID)

newMMUser := &model.User{
Email: msUser.Mail,
RemoteId: &shortUserID,
RemoteId: &p.remoteID,
FirstName: msUser.DisplayName,
Username: username,
}
Expand Down Expand Up @@ -777,7 +771,7 @@ func (p *Plugin) syncUsers() {
if err = p.store.SetUserInfo(newUser.Id, msUser.ID, nil); err != nil {
p.API.LogWarn("Unable to set user info during sync user job", "user_id", newUser.Id, "teams_user_id", msUser.ID, "error", err.Error())
}
} else if mmUser.RemoteId != nil {
} else if p.IsRemoteUser(mmUser) {
shouldUpdate := false
if !strings.HasPrefix(mmUser.Username, "msteams_") && username != mmUser.Username {
mmUser.Username = username
Expand Down Expand Up @@ -848,9 +842,9 @@ func getRandomString(characterSet string, length int) string {
return randomString.String()
}

// isRemoteUser returns true if the given user is a remote user managed by this plugin.
func isRemoteUser(user *model.User) bool {
return user.RemoteId != nil && *user.RemoteId != "" && strings.HasPrefix(user.Username, "msteams_")
// IsRemoteUser returns true if the given user is a remote user managed by this plugin.
func (p *Plugin) IsRemoteUser(user *model.User) bool {
return user.RemoteId != nil && *user.RemoteId == p.remoteID
}

func (p *Plugin) updateMetrics() {
Expand Down
1 change: 1 addition & 0 deletions server/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func newTestPlugin(t *testing.T) *Plugin {
clientBuilderWithToken: func(redirectURL, tenantID, clientId, clientSecret string, token *oauth2.Token, apiClient *pluginapi.LogService) msteams.Client {
return clientMock
},
remoteID: "remote-id",
}
plugin.store.(*storemocks.Store).On("Shutdown").Return(nil)
plugin.store.(*storemocks.Store).Test(t)
Expand Down
2 changes: 1 addition & 1 deletion server/selective_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (p *Plugin) ChatMembersSpanPlatforms(members model.ChannelMembers) (bool, *
return false, appErr
}

if isRemoteUser(user) {
if p.IsRemoteUser(user) {
// Synthetic users are always remote.
atLeastOneRemoteUser = true
} else if p.getPrimaryPlatform(user.Id) == PreferenceValuePlatformMSTeams {
Expand Down
33 changes: 17 additions & 16 deletions server/selective_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func setupPlugin(t *testing.T) *Plugin {
t.Helper()

p := &Plugin{}
p.remoteID = "remote-id"

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
Expand Down Expand Up @@ -122,11 +123,11 @@ func TestChatSpansPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

user2.RemoteId = model.NewString(model.NewId())
user2.RemoteId = model.NewString("remote-id")
user2, appErr = p.API.UpdateUser(user2)
require.Nil(t, appErr)

Expand All @@ -145,7 +146,7 @@ func TestChatSpansPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

Expand Down Expand Up @@ -196,15 +197,15 @@ func TestChatSpansPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

user2.RemoteId = model.NewString(model.NewId())
user2.RemoteId = model.NewString("remote-id")
user2, appErr = p.API.UpdateUser(user2)
require.Nil(t, appErr)

user3.RemoteId = model.NewString(model.NewId())
user3.RemoteId = model.NewString("remote-id")
user3, appErr = p.API.UpdateUser(user3)
require.Nil(t, appErr)

Expand All @@ -224,11 +225,11 @@ func TestChatSpansPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

user3.RemoteId = model.NewString(model.NewId())
user3.RemoteId = model.NewString("remote-id")
user3, appErr = p.API.UpdateUser(user3)
require.Nil(t, appErr)

Expand Down Expand Up @@ -296,11 +297,11 @@ func TestChatMembersSpanPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

user2.RemoteId = model.NewString(model.NewId())
user2.RemoteId = model.NewString("remote-id")
user2, appErr = p.API.UpdateUser(user2)
require.Nil(t, appErr)

Expand All @@ -319,7 +320,7 @@ func TestChatMembersSpanPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

Expand Down Expand Up @@ -374,15 +375,15 @@ func TestChatMembersSpanPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

user2.RemoteId = model.NewString(model.NewId())
user2.RemoteId = model.NewString("remote-id")
user2, appErr = p.API.UpdateUser(user2)
require.Nil(t, appErr)

user3.RemoteId = model.NewString(model.NewId())
user3.RemoteId = model.NewString("remote-id")
user3, appErr = p.API.UpdateUser(user3)
require.Nil(t, appErr)

Expand All @@ -403,11 +404,11 @@ func TestChatMembersSpanPlatforms(t *testing.T) {

var appErr *model.AppError

user1.RemoteId = model.NewString(model.NewId())
user1.RemoteId = model.NewString("remote-id")
user1, appErr = p.API.UpdateUser(user1)
require.Nil(t, appErr)

user3.RemoteId = model.NewString(model.NewId())
user3.RemoteId = model.NewString("remote-id")
user3, appErr = p.API.UpdateUser(user3)
require.Nil(t, appErr)

Expand Down
Loading
Loading