From 000aa0d891202d8bf8a7e3309fc5f0208578301d Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Mon, 8 Jan 2024 12:12:18 -0500 Subject: [PATCH] Plugin API for Shared Channels: support auto invite (#25834) * option for auto inviting plugin to all shared channels. * auto-invite remotes to shared channels when flag set * fix unit test --------- Co-authored-by: Mattermost Build --- server/channels/app/shared_channel.go | 3 +++ .../store/sqlstore/remote_cluster_store.go | 4 ++++ .../platform/services/remotecluster/ping.go | 2 +- .../services/sharedchannel/sync_send.go | 10 +++++++++ .../sharedchannel/sync_send_remote.go | 17 +++++++++++++- .../platform/services/sharedchannel/util.go | 13 +++++++++++ server/public/model/remote_cluster.go | 22 +++++++++++++++---- server/public/model/shared_channel.go | 4 ++++ 8 files changed, 69 insertions(+), 6 deletions(-) diff --git a/server/channels/app/shared_channel.go b/server/channels/app/shared_channel.go index 26626604a8..ea72b975e4 100644 --- a/server/channels/app/shared_channel.go +++ b/server/channels/app/shared_channel.go @@ -9,6 +9,7 @@ import ( "net/http" "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/store" ) @@ -325,11 +326,13 @@ func (a *App) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluste func (a *App) OnSharedChannelsPing(rc *model.RemoteCluster) bool { pluginsEnvironment := a.GetPluginsEnvironment() if pluginsEnvironment == nil { + a.Log().Error("Ping for shared channels cannot get plugins env") return false } pluginHooks, err := pluginsEnvironment.HooksForPlugin(rc.PluginID) if err != nil { + a.Log().Error("Ping for shared channels cannot get plugin hooks", mlog.String("plugin_id", rc.PluginID), mlog.Err(err)) return false } diff --git a/server/channels/store/sqlstore/remote_cluster_store.go b/server/channels/store/sqlstore/remote_cluster_store.go index 3d2f45a4e4..59cf9b4c26 100644 --- a/server/channels/store/sqlstore/remote_cluster_store.go +++ b/server/channels/store/sqlstore/remote_cluster_store.go @@ -191,6 +191,10 @@ func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([] query = query.Where(sq.Eq{"rc.PluginID": filter.PluginID}) } + if filter.RequireOptions != 0 { + query = query.Where(sq.NotEq{fmt.Sprintf("(rc.Options & %d)", filter.RequireOptions): 0}) + } + if filter.Topic != "" { trimmed := strings.TrimSpace(filter.Topic) if trimmed == "" || trimmed == "*" { diff --git a/server/platform/services/remotecluster/ping.go b/server/platform/services/remotecluster/ping.go index c90dd4838d..a03645c50c 100644 --- a/server/platform/services/remotecluster/ping.go +++ b/server/platform/services/remotecluster/ping.go @@ -45,7 +45,7 @@ func (rcs *Service) pingGenerator(pingChan chan *model.RemoteCluster, done <-cha } for _, rc := range remotes { - if rc.SiteURL != "" { // filter out unconfirmed invites + if rc.SiteURL != "" || rc.PluginID != "" { // filter out unconfirmed invites pingChan <- rc } } diff --git a/server/platform/services/sharedchannel/sync_send.go b/server/platform/services/sharedchannel/sync_send.go index ddcf65a6c5..3749a64c46 100644 --- a/server/platform/services/sharedchannel/sync_send.go +++ b/server/platform/services/sharedchannel/sync_send.go @@ -248,6 +248,16 @@ func (scs *Service) processTask(task syncTask) error { if err != nil { return err } + + // add all remotes that have the autoinvited option. + filter = model.RemoteClusterQueryFilter{ + RequireOptions: model.BitflagOptionAutoInvited, + } + remotesAutoInvited, err := scs.server.GetStore().RemoteCluster().GetAll(filter) + if err != nil { + return err + } + remotes = append(remotes, remotesAutoInvited...) } else { rc, err := scs.server.GetStore().RemoteCluster().Get(task.remoteID) if err != nil { diff --git a/server/platform/services/sharedchannel/sync_send_remote.go b/server/platform/services/sharedchannel/sync_send_remote.go index a0d5ea3c4e..0c031378ab 100644 --- a/server/platform/services/sharedchannel/sync_send_remote.go +++ b/server/platform/services/sharedchannel/sync_send_remote.go @@ -79,7 +79,22 @@ func (scs *Service) syncForRemote(task syncTask, rc *model.RemoteCluster) error } scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(task.channelID, rc.RemoteId) - if err != nil { + if isNotFoundError(err) && rc.IsOptionFlagSet(model.BitflagOptionAutoInvited) { + // if SharedChannelRemote not found and remote has autoinvite flag, create a scr for it, thus inviting the remote. + scr = &model.SharedChannelRemote{ + Id: model.NewId(), + ChannelId: task.channelID, + CreatorId: rc.CreatorId, + IsInviteAccepted: true, + IsInviteConfirmed: true, + RemoteId: rc.RemoteId, + LastPostCreateAt: model.GetMillis(), + LastPostUpdateAt: model.GetMillis(), + } + if scr, err = scs.server.GetStore().SharedChannel().SaveRemote(scr); err != nil { + return fmt.Errorf("cannot auto-create shared channel remote (channel_id=%s, remote_id=%s): %w", task.channelID, rc.RemoteId, err) + } + } else if err != nil { return err } diff --git a/server/platform/services/sharedchannel/util.go b/server/platform/services/sharedchannel/util.go index dde1e3fddc..2a3dc43776 100644 --- a/server/platform/services/sharedchannel/util.go +++ b/server/platform/services/sharedchannel/util.go @@ -105,6 +105,10 @@ func mungEmail(remotename string, maxLen int) string { } func isConflictError(err error) (string, bool) { + if err == nil { + return "", false + } + var errConflict *store.ErrConflict if errors.As(err, &errConflict) { return strings.ToLower(errConflict.Resource), true @@ -117,3 +121,12 @@ func isConflictError(err error) (string, bool) { } return "", false } + +func isNotFoundError(err error) bool { + if err == nil { + return false + } + + var errNotFound *store.ErrNotFound + return errors.As(err, &errNotFound) +} diff --git a/server/public/model/remote_cluster.go b/server/public/model/remote_cluster.go index 5f43ba2613..758a564ed3 100644 --- a/server/public/model/remote_cluster.go +++ b/server/public/model/remote_cluster.go @@ -22,7 +22,8 @@ const ( RemoteNameMinLength = 1 RemoteNameMaxLength = 64 - BitflagOptionAutoShareDMs Bitmask = 1 << iota + BitflagOptionAutoShareDMs Bitmask = 1 << iota // Any new DM/GM is automatically shared + BitflagOptionAutoInvited // Remote is automatically invited to all shared channels ) var ( @@ -31,6 +32,18 @@ var ( type Bitmask uint32 +func (bm *Bitmask) IsBitSet(flag Bitmask) bool { + return *bm != 0 +} + +func (bm *Bitmask) SetBit(flag Bitmask) { + *bm |= flag +} + +func (bm *Bitmask) UnsetBit(flag Bitmask) { + *bm &= ^flag +} + type RemoteCluster struct { RemoteId string `json:"remote_id"` RemoteTeamId string `json:"remote_team_id"` @@ -105,15 +118,15 @@ func (rc *RemoteCluster) IsValid() *AppError { } func (rc *RemoteCluster) IsOptionFlagSet(flag Bitmask) bool { - return rc.Options&flag != 0 + return rc.Options.IsBitSet(flag) } func (rc *RemoteCluster) SetOptionFlag(flag Bitmask) { - rc.Options |= flag + rc.Options.SetBit(flag) } func (rc *RemoteCluster) UnsetOptionFlag(flag Bitmask) { - rc.Options &= ^flag + rc.Options.UnsetBit(flag) } func IsValidRemoteName(s string) bool { @@ -340,4 +353,5 @@ type RemoteClusterQueryFilter struct { CreatorId string OnlyConfirmed bool PluginID string + RequireOptions Bitmask } diff --git a/server/public/model/shared_channel.go b/server/public/model/shared_channel.go index c0bdeea823..842f7acc01 100644 --- a/server/public/model/shared_channel.go +++ b/server/public/model/shared_channel.go @@ -305,6 +305,7 @@ type RegisterPluginOpts struct { PluginID string // id of this plugin registering CreatorID string // id of the user/bot registering AutoShareDMs bool // when true, all DMs are automatically shared to this remote + AutoInvited bool // when true, the plugin is automatically invited and sync'd with all shared channels. } // GetOptionFlags returns a Bitmask of option flags as specified by the boolean options. @@ -313,5 +314,8 @@ func (po RegisterPluginOpts) GetOptionFlags() Bitmask { if po.AutoShareDMs { flags |= BitflagOptionAutoShareDMs } + if po.AutoInvited { + flags |= BitflagOptionAutoInvited + } return flags }