mattermost/server/platform/services/sharedchannel/service_api.go
Doug Lauder 6aaabfb376
MM-57786 Fix Shared Channels plugin api (#26753)
* always ping on plugin registration; SharedChannel.IsValid allow no team for GM

* wait for services to start before ping

* ping plugin remotes synchronously on startup

* remove the waitForInterClusterServices stuff

* don't set remoteid when inviting remote to channel

* Update server/public/model/remote_cluster_test.go

Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>

* address review comments

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>
2024-04-15 16:18:25 -04:00

237 lines
8.5 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sharedchannel
import (
"errors"
"fmt"
"net/http"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/store"
)
// ShareChannel marks a local channel as shared. If the channel is already shared this method has
// no effect and returns without error.
// TeamId, type, displayname, purpose, and header are fetched from the channel if not provided.
func (scs *Service) ShareChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
channel, err := scs.server.GetStore().Channel().Get(sc.ChannelId, true)
if err != nil {
return nil, fmt.Errorf("cannot fetch channel while sharing channel %s: %w", sc.ChannelId, err)
}
// check if channel is already shared
scExisting, err := scs.server.GetStore().SharedChannel().Get(sc.ChannelId)
if err == nil {
// already shared, nothing to do
return scExisting, nil
}
if !isNotFoundError(err) {
return nil, fmt.Errorf("cannot check if channel %s is shared: %w", sc.ChannelId, err)
}
if sc.TeamId == "" {
sc.TeamId = channel.TeamId
}
if sc.Type == "" {
sc.Type = channel.Type
}
if sc.ShareName == "" {
sc.ShareName = channel.Name
}
if sc.ShareDisplayName == "" {
sc.ShareDisplayName = channel.DisplayName
}
if sc.SharePurpose == "" {
sc.SharePurpose = channel.Purpose
}
if sc.ShareHeader == "" {
sc.ShareHeader = channel.Header
}
if sc.CreatorId == "" {
sc.CreatorId = channel.CreatorId
}
// stores the SharedChannel and sets the share flag on the channel.
scNew, err := scs.server.GetStore().SharedChannel().Save(sc)
if err != nil {
return nil, err
}
scs.notifyClientsForSharedChannelConverted(channel)
return scNew, nil
}
// UpdateSharedChannel updates the shared channel details such as displayname, purpose, or header
func (scs *Service) UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
channel, err := scs.server.GetStore().Channel().Get(sc.ChannelId, true)
if err != nil {
return nil, err
}
scUpdated, err := scs.server.GetStore().SharedChannel().Update(sc)
if err != nil {
return nil, err
}
scs.notifyClientsForSharedChannelUpdate(channel)
return scUpdated, nil
}
// UnshareChannel unshared the channel by deleting the SharedChannels record and unsets the Channel `shared` flag.
// Returns true if a shared channel existed and was deleted.
func (scs *Service) UnshareChannel(channelID string) (bool, error) {
channel, err := scs.server.GetStore().Channel().Get(channelID, true)
if err != nil {
return false, err
}
// deletes the ShareChannel, unsets the share flag on the channel, deletes all records in SharedChannelRemotes for the channel.
deleted, err := scs.server.GetStore().SharedChannel().Delete(channelID)
if err != nil {
return false, err
}
scs.notifyClientsForSharedChannelConverted(channel)
return deleted, nil
}
// InviteRemoteToChannel sends an invite to the remote to a shared channel. If `shareIfNotShared` is true
// then the channel is marked as `shared` first if needed.
func (scs *Service) InviteRemoteToChannel(channelID, remoteID, userID string, shareIfNotShared bool) error {
scStore := scs.server.GetStore().SharedChannel()
rcStore := scs.server.GetStore().RemoteCluster()
// check if remote already invited to channel
hasRemote, err := scStore.HasRemote(channelID, remoteID)
if err != nil {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.fetch_remote.error",
map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError)
}
if hasRemote {
// already invited, nothing to do
return nil
}
// set the channel `shared` flag if needed
if shareIfNotShared {
sc := &model.SharedChannel{
ChannelId: channelID,
CreatorId: userID,
Home: true,
RemoteId: "", // channel originates locally
}
if _, err = scs.ShareChannel(sc); err != nil {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.share_channel.error",
map[string]any{"Error": err.Error()}, "", http.StatusBadRequest)
}
} else {
if err = scs.CheckChannelIsShared(channelID); err != nil {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_not_shared.error",
map[string]any{"ChannelID": channelID}, "", http.StatusBadRequest)
}
}
rc, err := rcStore.Get(remoteID)
if err != nil {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.remote_id_invalid.error",
map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
}
// don't allow invitation to shared channel originating from remote.
// (also blocks cyclic invitations)
if err = scs.CheckCanInviteToSharedChannel(channelID); err != nil {
if errors.Is(err, model.ErrChannelHomedOnRemote) {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite_not_home.error", nil, "", http.StatusInternalServerError)
}
scs.server.Log().Debug("InviteRemoteToChannel failed to check if can-invite",
mlog.String("name", rc.Name),
mlog.String("channel_id", channelID),
mlog.Err(err),
)
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "CheckCanInviteToSharedChannel", http.StatusInternalServerError).Wrap(err)
}
channel, err := scs.server.GetStore().Channel().Get(channelID, true)
if err != nil {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
}
// send channel invite to remote cluster. Will notify clients of channel change.
if err := scs.SendChannelInvite(channel, userID, rc); err != nil {
return model.NewAppError("InviteRemoteToChannel", "api.command_share.channel_invite.error",
map[string]any{"Name": rc.DisplayName, "Error": err.Error()}, "", http.StatusInternalServerError).Wrap(err)
}
return nil
}
func (scs *Service) UninviteRemoteFromChannel(channelID, remoteID string) error {
scr, err := scs.server.GetStore().SharedChannel().GetRemoteByIds(channelID, remoteID)
if err != nil || scr.ChannelId != channelID {
return model.NewAppError("UninviteRemoteFromChannel", "api.command_share.channel_remote_id_not_exists",
map[string]any{"RemoteId": remoteID}, "", http.StatusInternalServerError)
}
deleted, err := scs.server.GetStore().SharedChannel().DeleteRemote(scr.Id)
if err != nil || !deleted {
code := http.StatusInternalServerError
if err == nil {
err = errors.New("not found")
code = http.StatusBadRequest
}
return model.NewAppError("UninviteRemoteFromChannel", "api.command_share.could_not_uninvite.error",
map[string]any{"RemoteId": remoteID, "Error": err.Error()}, "", code)
}
return nil
}
// CheckChannelNotShared returns nil only if the channel is not already shared. Otherwise ErrChannelAlreadyShared is
// returned if the channel is shared, or database error.
func (scs *Service) CheckChannelNotShared(channelID string) error {
// check that channel exists.
if _, err := scs.server.GetStore().Channel().Get(channelID, true); err != nil {
return fmt.Errorf("cannot find channel %s: %w", channelID, err)
}
// Check channel is not already shared.
if _, err := scs.server.GetStore().SharedChannel().Get(channelID); err == nil {
return model.ErrChannelAlreadyShared
}
return nil
}
// CheckChannelIsShared returns nil only if the channel is shared. Otherwise a store.ErrNotFound is returned
// or database error.
func (scs *Service) CheckChannelIsShared(channelID string) error {
if _, err := scs.server.GetStore().SharedChannel().Get(channelID); err != nil {
var errNotFound *store.ErrNotFound
if errors.As(err, &errNotFound) {
return fmt.Errorf("channel is not shared: %w", errNotFound)
}
return fmt.Errorf("cannot check if channel %s is shared: %w", channelID, err)
}
return nil
}
// CheckCanInviteToSharedChannel checks if an invitation can be sent for the specified channel.
// - don't allow invitations to a shared channel originating from remote.
// - block cyclic invitations
// - the channel must exist
func (scs *Service) CheckCanInviteToSharedChannel(channelId string) error {
sc, err := scs.server.GetStore().SharedChannel().Get(channelId)
if err != nil {
if isNotFoundError(err) {
return fmt.Errorf("channel is not shared: %w", err)
}
return fmt.Errorf("cannot find channel: %w", err)
}
if !sc.Home {
return model.ErrChannelHomedOnRemote
}
return nil
}