Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion server/channels/api4/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2910,7 +2910,11 @@ func getChannelAccessControlAttributes(c *Context, w http.ResponseWriter, r *htt
return
}

attributes, err := c.App.GetAccessControlPolicyAttributes(c.AppContext, c.Params.ChannelId, "*")
// Channel banners care about the membership rule — the attributes that
// determine who can be in the channel. Since the v0.3 migration stores the
// action as "membership" rather than "*", ask for it explicitly; the
// wildcard fallback in GetRule still covers older policies that kept "*".
attributes, err := c.App.GetAccessControlPolicyAttributes(c.AppContext, c.Params.ChannelId, model.AccessControlPolicyActionMembership)
if err != nil {
c.Err = err
return
Expand Down
2 changes: 2 additions & 0 deletions server/channels/api4/remote_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,8 @@ func patchRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

updatedRC.Sanitize()

auditRec.Success()
auditRec.AddEventResultState(updatedRC)

Expand Down
11 changes: 8 additions & 3 deletions server/channels/api4/remote_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,9 +545,10 @@ func TestGenerateRemoteClusterInvite(t *testing.T) {
func TestGetRemoteCluster(t *testing.T) {
mainHelper.Parallel(t)
newRC := &model.RemoteCluster{
Name: "remotecluster",
SiteURL: "http://example.com",
Token: model.NewId(),
Name: "remotecluster",
SiteURL: "http://example.com",
Token: model.NewId(),
RemoteToken: model.NewId(),
}

t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
Expand Down Expand Up @@ -596,6 +597,7 @@ func TestGetRemoteCluster(t *testing.T) {
require.Equal(t, rc.RemoteId, fetchedRC.RemoteId)
require.Equal(t, th.BasicTeam.Id, fetchedRC.DefaultTeamId)
require.Empty(t, fetchedRC.Token)
require.Empty(t, fetchedRC.RemoteToken)
})
}

Expand Down Expand Up @@ -646,6 +648,7 @@ func TestPatchRemoteCluster(t *testing.T) {
DisplayName: "initialvalue",
SiteURL: "http://example.com",
Token: model.NewId(),
RemoteToken: model.NewId(),
}

rcp := &model.RemoteClusterPatch{DisplayName: model.NewPointer("different value")}
Expand Down Expand Up @@ -699,6 +702,8 @@ func TestPatchRemoteCluster(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "patched!", patchedRC.DisplayName)
require.Equal(t, newTeamId, patchedRC.DefaultTeamId)
require.Empty(t, patchedRC.Token)
require.Empty(t, patchedRC.RemoteToken)
})
}

Expand Down
25 changes: 13 additions & 12 deletions server/channels/app/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,19 @@ func (a *App) EnsureBot(rctx request.CTX, pluginID string, bot *model.Bot) (stri
if appErr := a.SetPluginKey(pluginID, botUserKey, []byte(user.Id)); appErr != nil {
return "", fmt.Errorf("failed to set plugin key: %w", appErr)
}
} else {
rctx.Logger().Error("Plugin attempted to use an account that already exists. Convert user to a bot "+
"account in the CLI by running 'mattermost user convert <username> --bot'. If the user is an "+
"existing user account you want to preserve, change its username and restart the Mattermost server, "+
"after which the plugin will create a bot account with that name. For more information about bot "+
"accounts, see https://mattermost.com/pl/default-bot-accounts", mlog.String("username",
bot.Username),
mlog.String("user_id",
user.Id),
)
}
return user.Id, nil
return user.Id, nil
}

rctx.Logger().Error("Plugin attempted to use an account that already exists. Convert user to a bot "+
"account in the CLI by running 'mattermost user convert <username> --bot'. If the user is an "+
"existing user account you want to preserve, change its username and restart the Mattermost server, "+
"after which the plugin will create a bot account with that name. For more information about bot "+
"accounts, see https://mattermost.com/pl/default-bot-accounts", mlog.String("username",
bot.Username),
mlog.String("user_id",
user.Id),
)
return "", fmt.Errorf("username %q is already taken by a non-bot user", bot.Username)
}

createdBot, err := a.CreateBot(rctx, bot)
Expand Down
16 changes: 16 additions & 0 deletions server/channels/app/bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ func TestEnsureBot(t *testing.T) {
assert.Equal(t, "another bot", bot.Description)
})

t.Run("ensure bot should fail if username belongs to a non-bot user", func(t *testing.T) {
th := Setup(t).InitBasic(t)

pluginId := "pluginId"

// th.BasicUser is a regular (non-bot) user created by InitBasic.
// EnsureBot must return an error — not the human user's ID.
botID, err := th.App.EnsureBot(th.Context, pluginId, &model.Bot{
Username: th.BasicUser.Username,
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Error(t, err)
assert.Empty(t, botID)
})

t.Run("ensure bot should pass even after delete bot user", func(t *testing.T) {
th := Setup(t).InitBasic(t)

Expand Down
85 changes: 83 additions & 2 deletions server/channels/app/platform/support_packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ package platform

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"runtime"
rpprof "runtime/pprof"
Expand All @@ -19,6 +23,8 @@ import (
"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/utils"
"github.com/mattermost/mattermost/server/v8/platform/shared/mail"
)

const (
Expand Down Expand Up @@ -221,6 +227,8 @@ func (ps *PlatformService) getSupportPacketDiagnostics(rctx request.CTX) (*model
}
d.LDAP.ServerName = severName
d.LDAP.ServerVersion = serverVersion
} else {
d.LDAP.Status = model.StatusDisabled
}

/* SAML */
Expand All @@ -231,14 +239,64 @@ func (ps *PlatformService) getSupportPacketDiagnostics(rctx request.CTX) (*model
/* Elastic Search */
if se := ps.SearchEngine.ElasticsearchEngine; se != nil {
d.ElasticSearch.Backend = *ps.Config().ElasticsearchSettings.Backend
d.ElasticSearch.ServerVersion = se.GetFullVersion()
d.ElasticSearch.ServerPlugins = se.GetPlugins()
if *ps.Config().ElasticsearchSettings.EnableIndexing {
appErr := se.TestConfig(rctx, ps.Config())
if appErr != nil {
d.ElasticSearch.Status = model.StatusFail
d.ElasticSearch.Error = appErr.Error()
} else {
d.ElasticSearch.Status = model.StatusOk
}
} else {
d.ElasticSearch.Status = model.StatusDisabled
}
d.ElasticSearch.ServerVersion = se.GetFullVersion()
d.ElasticSearch.ServerPlugins = se.GetPlugins()
} else {
d.ElasticSearch.Status = model.StatusDisabled
}

/* Email Notifications */
if model.SafeDereference(ps.Config().EmailSettings.SendEmailNotifications) {
emailSettings := ps.Config().EmailSettings
hostname := utils.GetHostnameFromSiteURL(model.SafeDereference(ps.Config().ServiceSettings.SiteURL))
mailCfg := &mail.SMTPConfig{
Hostname: hostname,
ConnectionSecurity: model.SafeDereference(emailSettings.ConnectionSecurity),
SkipServerCertificateVerification: model.SafeDereference(emailSettings.SkipServerCertificateVerification),
ServerName: model.SafeDereference(emailSettings.SMTPServer),
Server: model.SafeDereference(emailSettings.SMTPServer),
Port: model.SafeDereference(emailSettings.SMTPPort),
ServerTimeout: model.SafeDereference(emailSettings.SMTPServerTimeout),
Username: model.SafeDereference(emailSettings.SMTPUsername),
Password: model.SafeDereference(emailSettings.SMTPPassword),
EnableSMTPAuth: model.SafeDereference(emailSettings.EnableSMTPAuth),
SendEmailNotifications: true,
FeedbackName: model.SafeDereference(emailSettings.FeedbackName),
FeedbackEmail: model.SafeDereference(emailSettings.FeedbackEmail),
ReplyToAddress: model.SafeDereference(emailSettings.ReplyToAddress),
}
if smtpErr := mail.TestConnection(mailCfg); smtpErr != nil {
d.Notifications.Email.Status = model.StatusFail
d.Notifications.Email.Error = smtpErr.Error()
} else {
d.Notifications.Email.Status = model.StatusOk
}
} else {
d.Notifications.Email.Status = model.StatusDisabled
}

/* Push Notifications */
if model.SafeDereference(ps.Config().EmailSettings.SendPushNotifications) {
pushServerURL := model.SafeDereference(ps.Config().EmailSettings.PushNotificationServer)
if pushErr := testPushProxyConnection(rctx.Context(), pushServerURL); pushErr != nil {
d.Notifications.Push.Status = model.StatusFail
d.Notifications.Push.Error = pushErr.Error()
} else {
d.Notifications.Push.Status = model.StatusOk
}
} else {
d.Notifications.Push.Status = model.StatusDisabled
}

b, err := yaml.Marshal(&d)
Expand All @@ -253,6 +311,29 @@ func (ps *PlatformService) getSupportPacketDiagnostics(rctx request.CTX) (*model
return fileData, rErr.ErrorOrNil()
}

// TODO: move this into its own push proxy package once one exists (see also pushNotificationClient in server.go)
func testPushProxyConnection(ctx context.Context, serverURL string) error {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
versionURL, err := url.JoinPath(serverURL, "version")
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionURL, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("push proxy returned unexpected status %d", resp.StatusCode)
}
return nil
}

func (ps *PlatformService) getSanitizedConfigFile(rctx request.CTX) (*model.FileData, error) {
config := ps.getSanitizedConfig(rctx, &model.SanitizeOptions{PartiallyRedactDataSources: true})
spConfig := model.SupportPacketConfig{
Expand Down
Loading
Loading