diff --git a/server/channels/db/migrations/migrations.list b/server/channels/db/migrations/migrations.list index 5d4755c80a..1f27d892ca 100644 --- a/server/channels/db/migrations/migrations.list +++ b/server/channels/db/migrations/migrations.list @@ -234,6 +234,8 @@ channels/db/migrations/mysql/000117_msteams_shared_channels.down.sql channels/db/migrations/mysql/000117_msteams_shared_channels.up.sql channels/db/migrations/mysql/000118_create_index_poststats.down.sql channels/db/migrations/mysql/000118_create_index_poststats.up.sql +channels/db/migrations/mysql/000119_msteams_shared_channels_opts.down.sql +channels/db/migrations/mysql/000119_msteams_shared_channels_opts.up.sql channels/db/migrations/postgres/000001_create_teams.down.sql channels/db/migrations/postgres/000001_create_teams.up.sql channels/db/migrations/postgres/000002_create_team_members.down.sql @@ -468,3 +470,5 @@ channels/db/migrations/postgres/000117_msteams_shared_channels.down.sql channels/db/migrations/postgres/000117_msteams_shared_channels.up.sql channels/db/migrations/postgres/000118_create_index_poststats.down.sql channels/db/migrations/postgres/000118_create_index_poststats.up.sql +channels/db/migrations/postgres/000119_msteams_shared_channels_opts.down.sql +channels/db/migrations/postgres/000119_msteams_shared_channels_opts.up.sql diff --git a/server/channels/db/migrations/mysql/000119_msteams_shared_channels_opts.down.sql b/server/channels/db/migrations/mysql/000119_msteams_shared_channels_opts.down.sql new file mode 100644 index 0000000000..2fbae03871 --- /dev/null +++ b/server/channels/db/migrations/mysql/000119_msteams_shared_channels_opts.down.sql @@ -0,0 +1,15 @@ +SET @preparedStatement = (SELECT IF( + EXISTS( + SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'RemoteClusters' + AND table_schema = DATABASE() + AND column_name = 'Options' + ), + 'ALTER TABLE RemoteClusters DROP COLUMN Options;', + 'SELECT 1;' +)); + +PREPARE removeColumnIfExists FROM @preparedStatement; +EXECUTE removeColumnIfExists; +DEALLOCATE PREPARE removeColumnIfExists; + diff --git a/server/channels/db/migrations/mysql/000119_msteams_shared_channels_opts.up.sql b/server/channels/db/migrations/mysql/000119_msteams_shared_channels_opts.up.sql new file mode 100644 index 0000000000..91ee9458bc --- /dev/null +++ b/server/channels/db/migrations/mysql/000119_msteams_shared_channels_opts.up.sql @@ -0,0 +1,15 @@ +SET @preparedStatement = (SELECT IF( + NOT EXISTS( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'RemoteClusters' + AND table_schema = DATABASE() + AND column_name = 'Options' + ), + 'ALTER TABLE RemoteClusters ADD COLUMN Options smallint NOT NULL DEFAULT 0;', + 'SELECT 1;' +)); + +PREPARE addColumnIfNotExists FROM @preparedStatement; +EXECUTE addColumnIfNotExists; +DEALLOCATE PREPARE addColumnIfNotExists; + diff --git a/server/channels/db/migrations/postgres/000119_msteams_shared_channels_opts.down.sql b/server/channels/db/migrations/postgres/000119_msteams_shared_channels_opts.down.sql new file mode 100644 index 0000000000..1e04d9d339 --- /dev/null +++ b/server/channels/db/migrations/postgres/000119_msteams_shared_channels_opts.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE RemoteClusters DROP COLUMN IF EXISTS Options; + diff --git a/server/channels/db/migrations/postgres/000119_msteams_shared_channels_opts.up.sql b/server/channels/db/migrations/postgres/000119_msteams_shared_channels_opts.up.sql new file mode 100644 index 0000000000..02ca925efe --- /dev/null +++ b/server/channels/db/migrations/postgres/000119_msteams_shared_channels_opts.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE RemoteClusters ADD COLUMN IF NOT EXISTS Options SMALLINT NOT NULL DEFAULT 0; + diff --git a/server/channels/store/sqlstore/remote_cluster_store.go b/server/channels/store/sqlstore/remote_cluster_store.go index 7f7d3b080c..7e577d8e1c 100644 --- a/server/channels/store/sqlstore/remote_cluster_store.go +++ b/server/channels/store/sqlstore/remote_cluster_store.go @@ -40,6 +40,7 @@ func remoteClusterFields(prefix string) []string { prefix + "Topics", prefix + "CreatorId", prefix + "PluginID", + prefix + "Options", } } @@ -51,10 +52,10 @@ func (s sqlRemoteClusterStore) Save(remoteCluster *model.RemoteCluster) (*model. query := `INSERT INTO RemoteClusters (RemoteId, RemoteTeamId, Name, DisplayName, SiteURL, CreateAt, - LastPingAt, Token, RemoteToken, Topics, CreatorId, PluginID) + LastPingAt, Token, RemoteToken, Topics, CreatorId, PluginID, Options) VALUES (:RemoteId, :RemoteTeamId, :Name, :DisplayName, :SiteURL, :CreateAt, - :LastPingAt, :Token, :RemoteToken, :Topics, :CreatorId, :PluginID)` + :LastPingAt, :Token, :RemoteToken, :Topics, :CreatorId, :PluginID, :Options)` if _, err := s.GetMasterX().NamedExec(query, remoteCluster); err != nil { return nil, errors.Wrap(err, "failed to save RemoteCluster") @@ -78,7 +79,8 @@ func (s sqlRemoteClusterStore) Update(remoteCluster *model.RemoteCluster) (*mode DisplayName = :DisplayName, SiteURL = :SiteURL, Topics = :Topics, - PluginID = :PluginID + PluginID = :PluginID, + Options = :Options WHERE RemoteId = :RemoteId AND Name = :Name` if _, err := s.GetMasterX().NamedExec(query, remoteCluster); err != nil { diff --git a/server/channels/store/storetest/remote_cluster_store.go b/server/channels/store/storetest/remote_cluster_store.go index 4557ed3c7b..9b40cbbb86 100644 --- a/server/channels/store/storetest/remote_cluster_store.go +++ b/server/channels/store/storetest/remote_cluster_store.go @@ -44,6 +44,7 @@ func testRemoteClusterSave(t *testing.T, rctx request.CTX, ss store.Store) { require.Equal(t, rc.SiteURL, rcSaved.SiteURL) require.Greater(t, rc.CreateAt, int64(0)) require.Equal(t, rc.LastPingAt, int64(0)) + require.Equal(t, rc.Options, model.Bitmask(0)) }) t.Run("Save missing display name", func(t *testing.T) { @@ -63,6 +64,32 @@ func testRemoteClusterSave(t *testing.T, rctx request.CTX, ss store.Store) { _, err := ss.RemoteCluster().Save(rc) require.Error(t, err) }) + + t.Run("Save for plugin with options", func(t *testing.T) { + rc := &model.RemoteCluster{ + Name: "plugin_remote", + SiteURL: "plugin.example.com", + CreatorId: model.NewId(), + PluginID: testPluginID, + Options: model.BitflagOptionAutoShareDMs, + } + + rcSaved, err := ss.RemoteCluster().Save(rc) + require.NoError(t, err) + require.Equal(t, testPluginID, rcSaved.PluginID) + require.Equal(t, model.BitflagOptionAutoShareDMs, rcSaved.Options) + require.True(t, rcSaved.IsOptionFlagSet(model.BitflagOptionAutoShareDMs)) + + rc.Name = "plugin_remote_2" + rc.SiteURL = "plugin2.example.com" + rc.UnsetOptionFlag(model.BitflagOptionAutoShareDMs) + + rcSaved, err = ss.RemoteCluster().Save(rc) + require.NoError(t, err) + require.Equal(t, testPluginID, rcSaved.PluginID) + require.Equal(t, model.Bitmask(0), rcSaved.Options) + require.False(t, rcSaved.IsOptionFlagSet(model.BitflagOptionAutoShareDMs)) + }) } func testRemoteClusterDelete(t *testing.T, rctx request.CTX, ss store.Store) { @@ -95,6 +122,7 @@ func testRemoteClusterGet(t *testing.T, rctx request.CTX, ss store.Store) { CreatorId: model.NewId(), PluginID: testPluginID, } + rc.SetOptionFlag(model.BitflagOptionAutoShareDMs) rcSaved, err := ss.RemoteCluster().Save(rc) require.NoError(t, err) @@ -102,6 +130,7 @@ func testRemoteClusterGet(t *testing.T, rctx request.CTX, ss store.Store) { require.NoError(t, err) require.Equal(t, rcSaved.RemoteId, rcGet.RemoteId) require.Equal(t, testPluginID, rcGet.PluginID) + require.True(t, rcGet.IsOptionFlagSet(model.BitflagOptionAutoShareDMs)) }) t.Run("Get not found", func(t *testing.T) { diff --git a/server/public/model/remote_cluster.go b/server/public/model/remote_cluster.go index 54a305b149..5f43ba2613 100644 --- a/server/public/model/remote_cluster.go +++ b/server/public/model/remote_cluster.go @@ -21,25 +21,30 @@ const ( RemoteOfflineAfterMillis = 1000 * 60 * 5 // 5 minutes RemoteNameMinLength = 1 RemoteNameMaxLength = 64 + + BitflagOptionAutoShareDMs Bitmask = 1 << iota ) var ( validRemoteNameChars = regexp.MustCompile(`^[a-zA-Z0-9\.\-\_]+$`) ) +type Bitmask uint32 + type RemoteCluster struct { - RemoteId string `json:"remote_id"` - RemoteTeamId string `json:"remote_team_id"` - Name string `json:"name"` - DisplayName string `json:"display_name"` - SiteURL string `json:"site_url"` - CreateAt int64 `json:"create_at"` - LastPingAt int64 `json:"last_ping_at"` - Token string `json:"token"` - RemoteToken string `json:"remote_token"` - Topics string `json:"topics"` - CreatorId string `json:"creator_id"` - PluginID string `json:"plugin_id"` // non-empty when sync message are to be delivered via plugin API + RemoteId string `json:"remote_id"` + RemoteTeamId string `json:"remote_team_id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + SiteURL string `json:"site_url"` + CreateAt int64 `json:"create_at"` + LastPingAt int64 `json:"last_ping_at"` + Token string `json:"token"` + RemoteToken string `json:"remote_token"` + Topics string `json:"topics"` + CreatorId string `json:"creator_id"` + PluginID string `json:"plugin_id"` // non-empty when sync message are to be delivered via plugin API + Options Bitmask `json:"options"` // bit-flag set of options } func (rc *RemoteCluster) Auditable() map[string]interface{} { @@ -53,6 +58,7 @@ func (rc *RemoteCluster) Auditable() map[string]interface{} { "last_ping_at": rc.LastPingAt, "creator_id": rc.CreatorId, "plugin_id": rc.PluginID, + "options": rc.Options, } } @@ -98,6 +104,18 @@ func (rc *RemoteCluster) IsValid() *AppError { return nil } +func (rc *RemoteCluster) IsOptionFlagSet(flag Bitmask) bool { + return rc.Options&flag != 0 +} + +func (rc *RemoteCluster) SetOptionFlag(flag Bitmask) { + rc.Options |= flag +} + +func (rc *RemoteCluster) UnsetOptionFlag(flag Bitmask) { + rc.Options &= ^flag +} + func IsValidRemoteName(s string) bool { if len(s) < RemoteNameMinLength || len(s) > RemoteNameMaxLength { return false