Merge branch 'master' into plugins-2

Этот коммит содержится в:
JoramWilander
2018-06-27 16:56:50 -04:00
родитель d7976549a0 437f9f5b64
Коммит 88c5e469ca
47 изменённых файлов: 12552 добавлений и 26582 удалений

Просмотреть файл

@@ -209,13 +209,20 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if rchannel, err := c.App.PatchChannel(oldChannel, patch, c.Session.UserId); err != nil {
rchannel, err := c.App.PatchChannel(oldChannel, patch, c.Session.UserId)
if err != nil {
c.Err = err
return
} else {
c.LogAudit("")
w.Write([]byte(rchannel.ToJson()))
}
err = c.App.FillInChannelProps(rchannel)
if err != nil {
c.Err = err
return
}
c.LogAudit("")
w.Write([]byte(rchannel.ToJson()))
}
func restoreChannel(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -361,6 +368,12 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
err = c.App.FillInChannelProps(channel)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channel.ToJson()))
}
@@ -444,13 +457,19 @@ func getPublicChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request
return
}
if channels, err := c.App.GetPublicChannelsForTeam(c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage); err != nil {
channels, err := c.App.GetPublicChannelsForTeam(c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage)
if err != nil {
c.Err = err
return
} else {
w.Write([]byte(channels.ToJson()))
}
err = c.App.FillInChannelsProps(channels)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channels.ToJson()))
}
func getDeletedChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -464,13 +483,19 @@ func getDeletedChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Reques
return
}
if channels, err := c.App.GetDeletedChannels(c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage); err != nil {
channels, err := c.App.GetDeletedChannels(c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage)
if err != nil {
c.Err = err
return
} else {
w.Write([]byte(channels.ToJson()))
}
err = c.App.FillInChannelsProps(channels)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channels.ToJson()))
}
func getPublicChannelsByIdsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -497,12 +522,19 @@ func getPublicChannelsByIdsForTeam(c *Context, w http.ResponseWriter, r *http.Re
return
}
if channels, err := c.App.GetPublicChannelsByIdsForTeam(c.Params.TeamId, channelIds); err != nil {
channels, err := c.App.GetPublicChannelsByIdsForTeam(c.Params.TeamId, channelIds)
if err != nil {
c.Err = err
return
} else {
w.Write([]byte(channels.ToJson()))
}
err = c.App.FillInChannelsProps(channels)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channels.ToJson()))
}
func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -521,15 +553,24 @@ func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Reques
return
}
if channels, err := c.App.GetChannelsForUser(c.Params.TeamId, c.Params.UserId); err != nil {
channels, err := c.App.GetChannelsForUser(c.Params.TeamId, c.Params.UserId)
if err != nil {
c.Err = err
return
} else if c.HandleEtag(channels.Etag(), "Get Channels", w, r) {
return
} else {
w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag())
w.Write([]byte(channels.ToJson()))
}
if c.HandleEtag(channels.Etag(), "Get Channels", w, r) {
return
}
err = c.App.FillInChannelsProps(channels)
if err != nil {
c.Err = err
return
}
w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag())
w.Write([]byte(channels.ToJson()))
}
func autocompleteChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -545,12 +586,15 @@ func autocompleteChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Requ
name := r.URL.Query().Get("name")
if channels, err := c.App.AutocompleteChannels(c.Params.TeamId, name); err != nil {
channels, err := c.App.AutocompleteChannels(c.Params.TeamId, name)
if err != nil {
c.Err = err
return
} else {
w.Write([]byte(channels.ToJson()))
}
// Don't fill in channels props, since unused by client and potentially expensive.
w.Write([]byte(channels.ToJson()))
}
func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -570,12 +614,15 @@ func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if channels, err := c.App.SearchChannels(c.Params.TeamId, props.Term); err != nil {
channels, err := c.App.SearchChannels(c.Params.TeamId, props.Term)
if err != nil {
c.Err = err
return
} else {
w.Write([]byte(channels.ToJson()))
}
// Don't fill in channels props, since unused by client and potentially expensive.
w.Write([]byte(channels.ToJson()))
}
func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -638,6 +685,12 @@ func getChannelByName(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
err = c.App.FillInChannelProps(channel)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channel.ToJson()))
}
@@ -660,6 +713,12 @@ func getChannelByNameForTeamName(c *Context, w http.ResponseWriter, r *http.Requ
return
}
err = c.App.FillInChannelProps(channel)
if err != nil {
c.Err = err
return
}
w.Write([]byte(channel.ToJson()))
}

Просмотреть файл

@@ -1127,15 +1127,8 @@ func closeBody(r *http.Response) {
type MattermostTestProvider struct {
}
func (m *MattermostTestProvider) GetIdentifier() string {
return model.SERVICE_GITLAB
}
func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User {
return model.UserFromJson(data)
}
func (m *MattermostTestProvider) GetAuthDataFromJson(data io.Reader) string {
authData := model.UserFromJson(data)
return authData.Email
user := model.UserFromJson(data)
user.AuthData = &user.Email
return user
}

Просмотреть файл

@@ -206,6 +206,9 @@ func New(options ...Option) (outApp *App, outErr error) {
}
app.initJobs()
app.AddLicenseListener(func() {
app.initJobs()
})
subpath, err := utils.GetSubpathFromConfig(app.Config())
if err != nil {

Просмотреть файл

@@ -397,13 +397,13 @@ func (a *App) UpdateChannelPrivacy(oldChannel *model.Channel, user *model.User)
}
func (a *App) postChannelPrivacyMessage(user *model.User, channel *model.Channel) *model.AppError {
privacy := (map[string]string{
model.CHANNEL_OPEN: "private_to_public",
model.CHANNEL_PRIVATE: "public_to_private",
message := (map[string]string{
model.CHANNEL_OPEN: utils.T("api.channel.change_channel_privacy.private_to_public"),
model.CHANNEL_PRIVATE: utils.T("api.channel.change_channel_privacy.public_to_private"),
})[channel.Type]
post := &model.Post{
ChannelId: channel.Id,
Message: utils.T("api.channel.change_channel_privacy." + privacy),
Message: message,
Type: model.POST_CHANGE_CHANNEL_PRIVACY,
UserId: user.Id,
Props: model.StringInterface{
@@ -1591,3 +1591,67 @@ func (a *App) ToggleMuteChannel(channelId string, userId string) *model.ChannelM
a.Srv.Store.Channel().UpdateMember(member)
return member
}
func (a *App) FillInChannelProps(channel *model.Channel) *model.AppError {
return a.FillInChannelsProps(&model.ChannelList{channel})
}
func (a *App) FillInChannelsProps(channelList *model.ChannelList) *model.AppError {
// Group the channels by team and call GetChannelsByNames just once per team.
channelsByTeam := make(map[string]model.ChannelList)
for _, channel := range *channelList {
channelsByTeam[channel.TeamId] = append(channelsByTeam[channel.TeamId], channel)
}
for teamId, channelList := range channelsByTeam {
allChannelMentions := make(map[string]bool)
channelMentions := make(map[*model.Channel][]string, len(channelList))
// Collect mentions across the channels so as to query just once for this team.
for _, channel := range channelList {
channelMentions[channel] = model.ChannelMentions(channel.Header)
for _, channelMention := range channelMentions[channel] {
allChannelMentions[channelMention] = true
}
}
allChannelMentionNames := make([]string, 0, len(allChannelMentions))
for channelName := range allChannelMentions {
allChannelMentionNames = append(allChannelMentionNames, channelName)
}
if len(allChannelMentionNames) > 0 {
mentionedChannels, err := a.GetChannelsByNames(allChannelMentionNames, teamId)
if err != nil {
return err
}
mentionedChannelsByName := make(map[string]*model.Channel)
for _, channel := range mentionedChannels {
mentionedChannelsByName[channel.Name] = channel
}
for _, channel := range channelList {
channelMentionsProp := make(map[string]interface{}, len(channelMentions[channel]))
for _, channelMention := range channelMentions[channel] {
if mentioned, ok := mentionedChannelsByName[channelMention]; ok {
if mentioned.Type == model.CHANNEL_OPEN {
channelMentionsProp[mentioned.Name] = map[string]interface{}{
"display_name": mentioned.DisplayName,
}
}
}
}
if len(channelMentionsProp) > 0 {
channel.AddProp("channel_mentions", channelMentionsProp)
} else if channel.Props != nil {
delete(channel.Props, "channel_mentions")
}
}
}
}
return nil
}

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPermanentDeleteChannel(t *testing.T) {
@@ -399,3 +400,208 @@ func TestAppUpdateChannelScheme(t *testing.T) {
t.Fatal("Wrong Channel SchemeId")
}
}
func TestFillInChannelProps(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
channelPublic1, err := th.App.CreateChannel(&model.Channel{DisplayName: "Public 1", Name: "public1", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelPublic1)
channelPublic2, err := th.App.CreateChannel(&model.Channel{DisplayName: "Public 2", Name: "public2", Type: model.CHANNEL_OPEN, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelPublic2)
channelPrivate, err := th.App.CreateChannel(&model.Channel{DisplayName: "Private", Name: "private", Type: model.CHANNEL_PRIVATE, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelPrivate)
otherTeamId := model.NewId()
otherTeam := &model.Team{
DisplayName: "dn_" + otherTeamId,
Name: "name" + otherTeamId,
Email: "success+" + otherTeamId + "@simulator.amazonses.com",
Type: model.TEAM_OPEN,
}
otherTeam, err = th.App.CreateTeam(otherTeam)
require.Nil(t, err)
defer th.App.PermanentDeleteTeam(otherTeam)
channelOtherTeam, err := th.App.CreateChannel(&model.Channel{DisplayName: "Other Team Channel", Name: "other-team", Type: model.CHANNEL_OPEN, TeamId: otherTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelOtherTeam)
// Note that purpose is intentionally plaintext below.
t.Run("single channels", func(t *testing.T) {
testCases := []struct {
Description string
Channel *model.Channel
ExpectedChannelProps map[string]interface{}
}{
{
"channel on basic team without references",
&model.Channel{
TeamId: th.BasicTeam.Id,
Header: "No references",
Purpose: "No references",
},
nil,
},
{
"channel on basic team",
&model.Channel{
TeamId: th.BasicTeam.Id,
Header: "~public1, ~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"display_name": "Public 1",
},
},
},
},
{
"channel on other team",
&model.Channel{
TeamId: otherTeam.Id,
Header: "~public1, ~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
map[string]interface{}{
"channel_mentions": map[string]interface{}{
"other-team": map[string]interface{}{
"display_name": "Other Team Channel",
},
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
err = th.App.FillInChannelProps(testCase.Channel)
require.Nil(t, err)
assert.Equal(t, testCase.ExpectedChannelProps, testCase.Channel.Props)
})
}
})
t.Run("multiple channels", func(t *testing.T) {
testCases := []struct {
Description string
Channels *model.ChannelList
ExpectedChannelProps map[string]interface{}
}{
{
"single channel on basic team",
&model.ChannelList{
{
Name: "test",
TeamId: th.BasicTeam.Id,
Header: "~public1, ~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
},
map[string]interface{}{
"test": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"display_name": "Public 1",
},
},
},
},
},
{
"multiple channels on basic team",
&model.ChannelList{
{
Name: "test",
TeamId: th.BasicTeam.Id,
Header: "~public1, ~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
{
Name: "test2",
TeamId: th.BasicTeam.Id,
Header: "~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
{
Name: "test3",
TeamId: th.BasicTeam.Id,
Header: "No references",
Purpose: "No references",
},
},
map[string]interface{}{
"test": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"display_name": "Public 1",
},
},
},
"test2": map[string]interface{}(nil),
"test3": map[string]interface{}(nil),
},
},
{
"multiple channels across teams",
&model.ChannelList{
{
Name: "test",
TeamId: th.BasicTeam.Id,
Header: "~public1, ~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
{
Name: "test2",
TeamId: otherTeam.Id,
Header: "~private, ~other-team",
Purpose: "~public2, ~private, ~other-team",
},
{
Name: "test3",
TeamId: th.BasicTeam.Id,
Header: "No references",
Purpose: "No references",
},
},
map[string]interface{}{
"test": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"display_name": "Public 1",
},
},
},
"test2": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"other-team": map[string]interface{}{
"display_name": "Other Team Channel",
},
},
},
"test3": map[string]interface{}(nil),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
err = th.App.FillInChannelsProps(testCase.Channels)
require.Nil(t, err)
for _, channel := range *testCase.Channels {
assert.Equal(t, testCase.ExpectedChannelProps[channel.Name], channel.Props)
}
})
}
})
}

Просмотреть файл

@@ -6,6 +6,7 @@ package app
import (
"github.com/mattermost/mattermost-server/model"
goi18n "github.com/nicksnyder/go-i18n/i18n"
"strings"
)
type JoinProvider struct {
@@ -34,12 +35,18 @@ func (me *JoinProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Comman
}
func (me *JoinProvider) DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
if result := <-a.Srv.Store.Channel().GetByName(args.TeamId, message, true); result.Err != nil {
channelName := message
if strings.HasPrefix(message, "~") {
channelName = message[1:]
}
if result := <-a.Srv.Store.Channel().GetByName(args.TeamId, channelName, true); result.Err != nil {
return &model.CommandResponse{Text: args.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
channel := result.Data.(*model.Channel)
if channel.Name == message {
if channel.Name == channelName {
allowed := false
if (channel.Type == model.CHANNEL_PRIVATE && a.SessionHasPermissionToChannel(args.Session, channel.Id, model.PERMISSION_READ_CHANNEL)) || channel.Type == model.CHANNEL_OPEN {
allowed = true

88
app/command_join_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,88 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/nicksnyder/go-i18n/i18n"
"github.com/stretchr/testify/assert"
)
func TestJoinCommandNoChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
if testing.Short() {
t.SkipNow()
}
cmd := &JoinProvider{}
resp := cmd.DoCommand(th.App, &model.CommandArgs{
T: i18n.IdentityTfunc(),
UserId: th.BasicUser2.Id,
SiteURL: "http://test.url",
TeamId: th.BasicTeam.Id,
}, "asdsad")
assert.Equal(t, "api.command_join.list.app_error", resp.Text)
}
func TestJoinCommandForExistingChannel(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
if testing.Short() {
t.SkipNow()
}
channel2, _ := th.App.CreateChannel(&model.Channel{
DisplayName: "AA",
Name: "aa" + model.NewId() + "a",
Type: model.CHANNEL_OPEN,
TeamId: th.BasicTeam.Id,
CreatorId: th.BasicUser.Id,
}, false)
cmd := &JoinProvider{}
resp := cmd.DoCommand(th.App, &model.CommandArgs{
T: i18n.IdentityTfunc(),
UserId: th.BasicUser2.Id,
SiteURL: "http://test.url",
TeamId: th.BasicTeam.Id,
}, channel2.Name)
assert.Equal(t, "", resp.Text)
assert.Equal(t, "http://test.url/"+th.BasicTeam.Name+"/channels/"+channel2.Name, resp.GotoLocation)
}
func TestJoinCommandWithTilde(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
if testing.Short() {
t.SkipNow()
}
channel2, _ := th.App.CreateChannel(&model.Channel{
DisplayName: "AA",
Name: "aa" + model.NewId() + "a",
Type: model.CHANNEL_OPEN,
TeamId: th.BasicTeam.Id,
CreatorId: th.BasicUser.Id,
}, false)
cmd := &JoinProvider{}
resp := cmd.DoCommand(th.App, &model.CommandArgs{
T: i18n.IdentityTfunc(),
UserId: th.BasicUser2.Id,
SiteURL: "http://test.url",
TeamId: th.BasicTeam.Id,
}, "~"+channel2.Name)
assert.Equal(t, "", resp.Text)
assert.Equal(t, "http://test.url/"+th.BasicTeam.Name+"/channels/"+channel2.Name, resp.GotoLocation)
}

Просмотреть файл

@@ -36,6 +36,7 @@ const (
TRACK_CONFIG_WEBRTC = "config_webrtc"
TRACK_CONFIG_SUPPORT = "config_support"
TRACK_CONFIG_NATIVEAPP = "config_nativeapp"
TRACK_CONFIG_EXPERIMENTAL = "config_experimental"
TRACK_CONFIG_ANALYTICS = "config_analytics"
TRACK_CONFIG_ANNOUNCEMENT = "config_announcement"
TRACK_CONFIG_ELASTICSEARCH = "config_elasticsearch"
@@ -252,6 +253,7 @@ func (a *App) trackConfig() {
"allow_cookies_for_subdomains": *cfg.ServiceSettings.AllowCookiesForSubdomains,
"enable_api_team_deletion": *cfg.ServiceSettings.EnableAPITeamDeletion,
"experimental_enable_hardened_mode": *cfg.ServiceSettings.ExperimentalEnableHardenedMode,
"experimental_limit_client_config": *cfg.ServiceSettings.ExperimentalLimitClientConfig,
})
a.SendDiagnostic(TRACK_CONFIG_TEAM, map[string]interface{}{
@@ -475,6 +477,11 @@ func (a *App) trackConfig() {
"isdefault_turn_uri": isDefault(*cfg.WebrtcSettings.TurnURI, model.WEBRTC_SETTINGS_DEFAULT_TURN_URI),
})
a.SendDiagnostic(TRACK_CONFIG_EXPERIMENTAL, map[string]interface{}{
"client_side_cert_enable": *cfg.ExperimentalSettings.ClientSideCertEnable,
"isdefault_client_side_cert_check": isDefault(*cfg.ExperimentalSettings.ClientSideCertCheck, model.CLIENT_SIDE_CERT_CHECK_PRIMARY_AUTH),
})
a.SendDiagnostic(TRACK_CONFIG_ANALYTICS, map[string]interface{}{
"isdefault_max_users_for_statistics": isDefault(*cfg.AnalyticsSettings.MaxUsersForStatistics, model.ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS),
})

Просмотреть файл

@@ -54,7 +54,7 @@ const (
MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image
IMAGE_THUMBNAIL_PIXEL_WIDTH = 120
IMAGE_THUMBNAIL_PIXEL_HEIGHT = 100
IMAGE_PREVIEW_PIXEL_WIDTH = 1024
IMAGE_PREVIEW_PIXEL_WIDTH = 1920
)
func (a *App) FileBackend() (utils.FileBackend, *model.AppError) {

Просмотреть файл

@@ -209,6 +209,9 @@ func (a *App) BulkImport(fileReader io.Reader, dryRun bool, workers int) (*model
scanner := bufio.NewScanner(fileReader)
lineNumber := 0
a.Srv.Store.LockToMaster()
defer a.Srv.Store.UnlockFromMaster()
errorsChan := make(chan LineImportWorkerError, (2*workers)+1) // size chosen to ensure it never gets filled up completely.
var wg sync.WaitGroup
var linesChan chan LineImportWorkerData

Просмотреть файл

@@ -457,7 +457,13 @@ func (a *App) LoginByOAuth(service string, userData io.Reader, teamId string) (*
return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error",
map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented)
} else {
authData = provider.GetAuthDataFromJson(bytes.NewReader(buf.Bytes()))
authUser := provider.GetUserFromJson(bytes.NewReader(buf.Bytes()))
if authUser.AuthData != nil {
authData = *authUser.AuthData
} else {
authData = ""
}
}
if len(authData) == 0 {

Просмотреть файл

@@ -44,6 +44,9 @@
"WebserverMode": "gzip",
"EnableCustomEmoji": false,
"EnableEmojiPicker": true,
"EnableGifPicker": true,
"GfycatApiKey": "",
"GfycatApiSecret": "",
"RestrictCustomEmojiCreation": "all",
"RestrictPostDelete": "all",
"AllowEditPost": "always",

Просмотреть файл

@@ -10,9 +10,7 @@ import (
)
type OauthProvider interface {
GetIdentifier() string
GetUserFromJson(data io.Reader) *model.User
GetAuthDataFromJson(data io.Reader) string
}
var oauthProviders = make(map[string]OauthProvider)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -99,6 +99,14 @@
"id": "api.channel.add_user_to_channel.type.app_error",
"translation": "Can not add user to this channel type"
},
{
"id": "api.channel.change_channel_privacy.private_to_public",
"translation": "This channel has been converted to a Public Channel and can be joined by any team member."
},
{
"id": "api.channel.change_channel_privacy.public_to_private",
"translation": "This channel has been converted to a Private Channel."
},
{
"id": "api.channel.convert_channel_to_private.default_channel_error",
"translation": "This default channel cannot be converted into a private channel."
@@ -1534,66 +1542,6 @@
"id": "api.slackimport.slack_import.zip.app_error",
"translation": "Unable to open the Slack export zip file.\r\n"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.rollback_transaction.app_error",
"translation": "Failed to rollback the database transaction"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.select.app_error",
"translation": "Failed to retrieve the users"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.update.app_error",
"translation": "Failed to update the user"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.rollback_transaction.app_error",
"translation": "Failed to rollback the database transaction"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.select.app_error",
"translation": "Failed to retrieve the team members"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.update.app_error",
"translation": "Failed to update the team member"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.rollback_transaction.app_error",
"translation": "Failed to rollback the database transaction"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.select.app_error",
"translation": "Failed to retrieve the channel members"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.update.app_error",
"translation": "Failed to update the channel member"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "api.status.user_not_found.app_error",
"translation": "User not found"
@@ -5126,6 +5074,26 @@
"id": "store.sql_channel.analytics_type_count.app_error",
"translation": "We couldn't get channel type counts"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.rollback_transaction.app_error",
"translation": "Failed to rollback the database transaction"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.select.app_error",
"translation": "Failed to retrieve the channel members"
},
{
"id": "store.sql_channel.clear_all_custom_role_assignments.update.app_error",
"translation": "Failed to update the channel member"
},
{
"id": "store.sql_channel.delete.channel.app_error",
"translation": "We couldn't delete the channel"
@@ -6110,6 +6078,26 @@
"id": "store.sql_team.analytics_team_count.app_error",
"translation": "We couldn't count the teams"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.rollback_transaction.app_error",
"translation": "Failed to rollback the database transaction"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.select.app_error",
"translation": "Failed to retrieve the team members"
},
{
"id": "store.sql_team.clear_all_custom_role_assignments.update.app_error",
"translation": "Failed to update the team member"
},
{
"id": "store.sql_team.get.find.app_error",
"translation": "We couldn't find the existing team"
@@ -6266,6 +6254,26 @@
"id": "store.sql_user.analytics_unique_user_count.app_error",
"translation": "We couldn't get the unique user count"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.commit_transaction.app_error",
"translation": "Failed to commit the database transaction"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.open_transaction.app_error",
"translation": "Failed to begin the database transaction"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.rollback_transaction.app_error",
"translation": "Failed to rollback the database transaction"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.select.app_error",
"translation": "Failed to retrieve the users"
},
{
"id": "store.sql_user.clear_all_custom_role_assignments.update.app_error",
"translation": "Failed to update the user"
},
{
"id": "store.sql_user.get.app_error",
"translation": "We encountered an error finding the account"

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -95,6 +95,8 @@ func (workers *Workers) Start() *Workers {
}
func (workers *Workers) handleConfigChange(oldConfig *model.Config, newConfig *model.Config) {
mlog.Debug("Workers received config change.")
if workers.DataRetention != nil {
if (!*oldConfig.DataRetentionSettings.EnableMessageDeletion && !*oldConfig.DataRetentionSettings.EnableFileDeletion) && (*newConfig.DataRetentionSettings.EnableMessageDeletion || *newConfig.DataRetentionSettings.EnableFileDeletion) {
go workers.DataRetention.Run()

Просмотреть файл

@@ -32,21 +32,22 @@ const (
)
type Channel struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
TeamId string `json:"team_id"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Header string `json:"header"`
Purpose string `json:"purpose"`
LastPostAt int64 `json:"last_post_at"`
TotalMsgCount int64 `json:"total_msg_count"`
ExtraUpdateAt int64 `json:"extra_update_at"`
CreatorId string `json:"creator_id"`
SchemeId *string `json:"scheme_id"`
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
TeamId string `json:"team_id"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Header string `json:"header"`
Purpose string `json:"purpose"`
LastPostAt int64 `json:"last_post_at"`
TotalMsgCount int64 `json:"total_msg_count"`
ExtraUpdateAt int64 `json:"extra_update_at"`
CreatorId string `json:"creator_id"`
SchemeId *string `json:"scheme_id"`
Props map[string]interface{} `json:"props" db:"-"`
}
type ChannelPatch struct {
@@ -163,6 +164,18 @@ func (o *Channel) Patch(patch *ChannelPatch) {
}
}
func (o *Channel) MakeNonNil() {
if o.Props == nil {
o.Props = make(map[string]interface{})
}
}
func (o *Channel) AddProp(key string, value interface{}) {
o.MakeNonNil()
o.Props[key] = value
}
func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 {
return userId2 + "__" + userId1

28
model/channel_mentions.go Обычный файл
Просмотреть файл

@@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"regexp"
"strings"
)
var channelMentionRegexp = regexp.MustCompile(`\B~[a-zA-Z0-9\-_]+`)
func ChannelMentions(message string) []string {
var names []string
if strings.Contains(message, "~") {
alreadyMentioned := make(map[string]bool)
for _, match := range channelMentionRegexp.FindAllString(message, -1) {
name := match[1:]
if !alreadyMentioned[name] {
names = append(names, name)
alreadyMentioned[name] = true
}
}
}
return names
}

Просмотреть файл

@@ -210,6 +210,9 @@ type ServiceSettings struct {
WebserverMode *string
EnableCustomEmoji *bool
EnableEmojiPicker *bool
EnableGifPicker *bool
GfycatApiKey *string
GfycatApiSecret *string
RestrictCustomEmojiCreation *string
RestrictPostDelete *string
AllowEditPost *string
@@ -413,6 +416,18 @@ func (s *ServiceSettings) SetDefaults() {
s.EnableEmojiPicker = NewBool(true)
}
if s.EnableGifPicker == nil {
s.EnableGifPicker = NewBool(true)
}
if s.GfycatApiKey == nil {
s.GfycatApiKey = NewString("")
}
if s.GfycatApiSecret == nil {
s.GfycatApiSecret = NewString("")
}
if s.RestrictCustomEmojiCreation == nil {
s.RestrictCustomEmojiCreation = NewString(RESTRICT_EMOJI_CREATION_ALL)
}

Просмотреть файл

@@ -47,7 +47,7 @@ func userFromGitLabUser(glu *GitLabUser) *model.User {
user.FirstName = glu.Name
}
user.Email = glu.Email
userId := strconv.FormatInt(glu.Id, 10)
userId := glu.getAuthData()
user.AuthData = &userId
user.AuthService = model.USER_AUTH_SERVICE_GITLAB
@@ -90,10 +90,6 @@ func (glu *GitLabUser) getAuthData() string {
return strconv.FormatInt(glu.Id, 10)
}
func (m *GitLabProvider) GetIdentifier() string {
return model.USER_AUTH_SERVICE_GITLAB
}
func (m *GitLabProvider) GetUserFromJson(data io.Reader) *model.User {
glu := gitLabUserFromJson(data)
if glu.IsValid() {
@@ -102,13 +98,3 @@ func (m *GitLabProvider) GetUserFromJson(data io.Reader) *model.User {
return &model.User{}
}
func (m *GitLabProvider) GetAuthDataFromJson(data io.Reader) string {
glu := gitLabUserFromJson(data)
if glu.IsValid() {
return glu.getAuthData()
}
return ""
}

Просмотреть файл

@@ -7,7 +7,6 @@ import (
"encoding/json"
"io"
"net/http"
"regexp"
"sort"
"strings"
"unicode/utf8"
@@ -343,20 +342,8 @@ func PostPatchFromJson(data io.Reader) *PostPatch {
return &post
}
var channelMentionRegexp = regexp.MustCompile(`\B~[a-zA-Z0-9\-_]+`)
func (o *Post) ChannelMentions() (names []string) {
if strings.Contains(o.Message, "~") {
alreadyMentioned := make(map[string]bool)
for _, match := range channelMentionRegexp.FindAllString(o.Message, -1) {
name := match[1:]
if !alreadyMentioned[name] {
names = append(names, name)
alreadyMentioned[name] = true
}
}
}
return
func (o *Post) ChannelMentions() []string {
return ChannelMentions(o.Message)
}
func (r *PostActionIntegrationRequest) ToJson() string {

Просмотреть файл

@@ -181,6 +181,14 @@ func (s *LayeredStore) Close() {
s.DatabaseLayer.Close()
}
func (s *LayeredStore) LockToMaster() {
s.DatabaseLayer.LockToMaster()
}
func (s *LayeredStore) UnlockFromMaster() {
s.DatabaseLayer.UnlockFromMaster()
}
func (s *LayeredStore) DropAllTables() {
s.DatabaseLayer.DropAllTables()
}

Просмотреть файл

@@ -298,7 +298,7 @@ func (s SqlChannelStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_channelmembers_channel_id", "ChannelMembers", "ChannelId")
s.CreateIndexIfNotExists("idx_channelmembers_user_id", "ChannelMembers", "UserId")
s.CreateFullTextIndexIfNotExists("idx_channels_txt", "Channels", "Name, DisplayName")
s.CreateFullTextIndexIfNotExists("idx_channel_search_txt", "Channels", "Name, DisplayName, Purpose")
}
func (s SqlChannelStore) Save(channel *model.Channel, maxChannelsPerTeam int64) store.StoreChannel {
@@ -1573,7 +1573,7 @@ func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) s
func (s SqlChannelStore) buildLIKEClause(term string) (likeClause, likeTerm string) {
likeTerm = term
searchColumns := "Name, DisplayName"
searchColumns := "Name, DisplayName, Purpose"
// These chars must be removed from the like query.
for _, c := range ignoreLikeSearchChar {
@@ -1608,7 +1608,7 @@ func (s SqlChannelStore) buildFulltextClause(term string) (fulltextClause, fullt
// Copy the terms as we will need to prepare them differently for each search type.
fulltextTerm = term
searchColumns := "Name, DisplayName"
searchColumns := "Name, DisplayName, Purpose"
// These chars must be treated as spaces in the fulltext query.
for _, c := range spaceFulltextSearchChar {

Просмотреть файл

@@ -65,6 +65,8 @@ type SqlStore interface {
RemoveIndexIfExists(indexName string, tableName string) bool
GetAllConns() []*gorp.DbMap
Close()
LockToMaster()
UnlockFromMaster()
Team() store.TeamStore
Channel() store.ChannelStore
Post() store.PostStore

Просмотреть файл

@@ -105,6 +105,7 @@ type SqlSupplier struct {
searchReplicas []*gorp.DbMap
oldStores SqlSupplierOldStores
settings *model.SqlSettings
lockedToMaster bool
}
func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlSupplier {
@@ -283,7 +284,7 @@ func (ss *SqlSupplier) GetSearchReplica() *gorp.DbMap {
}
func (ss *SqlSupplier) GetReplica() *gorp.DbMap {
if len(ss.settings.DataSourceReplicas) == 0 {
if len(ss.settings.DataSourceReplicas) == 0 || ss.lockedToMaster {
return ss.GetMaster()
}
@@ -801,6 +802,14 @@ func (ss *SqlSupplier) Close() {
}
}
func (ss *SqlSupplier) LockToMaster() {
ss.lockedToMaster = true
}
func (ss *SqlSupplier) UnlockFromMaster() {
ss.lockedToMaster = false
}
func (ss *SqlSupplier) Team() store.TeamStore {
return ss.oldStores.team
}

Просмотреть файл

@@ -446,6 +446,8 @@ func UpgradeDatabaseToVersion50(sqlStore SqlStore) {
sqlStore.GetMaster().Exec("UPDATE Roles SET SchemeManaged=false WHERE Name NOT IN ('system_user', 'system_admin', 'team_user', 'team_admin', 'channel_user', 'channel_admin')")
sqlStore.CreateColumnIfNotExists("IncomingWebhooks", "ChannelLocked", "boolean", "boolean", "0")
sqlStore.RemoveIndexIfExists("idx_channels_txt", "Channels")
saveSchemaVersion(sqlStore, VERSION_5_0_0)
}
}

Просмотреть файл

@@ -67,6 +67,8 @@ type Store interface {
Plugin() PluginStore
MarkSystemRanUnitTests()
Close()
LockToMaster()
UnlockFromMaster()
DropAllTables()
TotalMasterDbConnections() int
TotalReadDbConnections() int

Просмотреть файл

@@ -1708,6 +1708,14 @@ func testChannelStoreSearchMore(t *testing.T, ss store.Store) {
o8.Type = model.CHANNEL_PRIVATE
store.Must(ss.Channel().Save(&o8, -1))
o9 := model.Channel{}
o9.TeamId = o1.TeamId
o9.DisplayName = "Channel With Purpose"
o9.Purpose = "This can now be searchable!"
o9.Name = "with-purpose"
o9.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&o9, -1))
if result := <-ss.Channel().SearchMore(m1.UserId, o1.TeamId, "ChannelA"); result.Err != nil {
t.Fatal(result.Err)
} else {
@@ -1773,6 +1781,19 @@ func testChannelStoreSearchMore(t *testing.T, ss store.Store) {
}
}
if result := <-ss.Channel().SearchMore(m1.UserId, o1.TeamId, "now searchable"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o9.Name {
t.Fatal("wrong channel returned")
}
}
/*
// Disabling this check as it will fail on PostgreSQL as we have "liberalised" channel matching to deal with
// Full-Text Stemming Limitations.
@@ -1884,6 +1905,14 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) {
o11.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&o11, -1))
o12 := model.Channel{}
o12.TeamId = o1.TeamId
o12.DisplayName = "Channel With Purpose"
o12.Purpose = "This can now be searchable!"
o12.Name = "with-purpose"
o12.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&o12, -1))
for name, search := range map[string]func(teamId string, term string) store.StoreChannel{
"AutocompleteInTeam": ss.Channel().AutocompleteInTeam,
"SearchInTeam": ss.Channel().SearchInTeam,
@@ -1986,6 +2015,19 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) {
t.Fatal("wrong channel returned")
}
}
if result := <-search(o1.TeamId, "now searchable"); result.Err != nil {
t.Fatal(result.Err)
} else {
channels := result.Data.(*model.ChannelList)
if len(*channels) != 1 {
t.Fatal("should return 1 channel")
}
if (*channels)[0].Name != o12.Name {
t.Fatal("wrong channel returned")
}
}
})
}
}

Просмотреть файл

@@ -200,6 +200,11 @@ func (_m *LayeredStoreDatabaseLayer) License() store.LicenseStore {
return r0
}
// LockToMaster provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) LockToMaster() {
_m.Called()
}
// MarkSystemRanUnitTests provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) MarkSystemRanUnitTests() {
_m.Called()
@@ -851,6 +856,11 @@ func (_m *LayeredStoreDatabaseLayer) TotalSearchDbConnections() int {
return r0
}
// UnlockFromMaster provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) UnlockFromMaster() {
_m.Called()
}
// User provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) User() store.UserStore {
ret := _m.Called()

Просмотреть файл

@@ -411,6 +411,11 @@ func (_m *SqlStore) License() store.LicenseStore {
return r0
}
// LockToMaster provides a mock function with given fields:
func (_m *SqlStore) LockToMaster() {
_m.Called()
}
// MarkSystemRanUnitTests provides a mock function with given fields:
func (_m *SqlStore) MarkSystemRanUnitTests() {
_m.Called()
@@ -706,6 +711,11 @@ func (_m *SqlStore) TotalSearchDbConnections() int {
return r0
}
// UnlockFromMaster provides a mock function with given fields:
func (_m *SqlStore) UnlockFromMaster() {
_m.Called()
}
// User provides a mock function with given fields:
func (_m *SqlStore) User() store.UserStore {
ret := _m.Called()

Просмотреть файл

@@ -198,6 +198,11 @@ func (_m *Store) License() store.LicenseStore {
return r0
}
// LockToMaster provides a mock function with given fields:
func (_m *Store) LockToMaster() {
_m.Called()
}
// MarkSystemRanUnitTests provides a mock function with given fields:
func (_m *Store) MarkSystemRanUnitTests() {
_m.Called()
@@ -437,6 +442,11 @@ func (_m *Store) TotalSearchDbConnections() int {
return r0
}
// UnlockFromMaster provides a mock function with given fields:
func (_m *Store) UnlockFromMaster() {
_m.Called()
}
// User provides a mock function with given fields:
func (_m *Store) User() store.UserStore {
ret := _m.Called()

Просмотреть файл

@@ -77,6 +77,8 @@ func (s *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore {
}
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
func (s *Store) Close() { /* do nothing */ }
func (s *Store) LockToMaster() { /* do nothing */ }
func (s *Store) UnlockFromMaster() { /* do nothing */ }
func (s *Store) DropAllTables() { /* do nothing */ }
func (s *Store) TotalMasterDbConnections() int { return 1 }
func (s *Store) TotalReadDbConnections() int { return 1 }

Просмотреть файл

@@ -555,6 +555,9 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["SQLDriverName"] = *c.SqlSettings.DriverName
props["EnableEmojiPicker"] = strconv.FormatBool(*c.ServiceSettings.EnableEmojiPicker)
props["EnableGifPicker"] = strconv.FormatBool(*c.ServiceSettings.EnableGifPicker)
props["GfycatApiKey"] = *c.ServiceSettings.GfycatApiKey
props["GfycatApiSecret"] = *c.ServiceSettings.GfycatApiSecret
props["RestrictCustomEmojiCreation"] = *c.ServiceSettings.RestrictCustomEmojiCreation
props["MaxFileSize"] = strconv.FormatInt(*c.FileSettings.MaxFileSize, 10)

Просмотреть файл

@@ -157,7 +157,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.Err.IsOAuth = false
}
if IsApiCall(c.App, r) || len(r.Header.Get("X-Mobile-App")) > 0 {
if IsApiCall(c.App, r) || IsWebhookCall(c.App, r) || len(r.Header.Get("X-Mobile-App")) > 0 {
w.WriteHeader(c.Err.StatusCode)
w.Write([]byte(c.Err.ToJson()))
} else {

Просмотреть файл

@@ -73,7 +73,13 @@ func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) {
func IsApiCall(a *app.App, r *http.Request) bool {
subpath, _ := utils.GetSubpathFromConfig(a.Config())
return strings.Index(r.URL.Path, path.Join(subpath, "api")+"/") == 0
return strings.HasPrefix(r.URL.Path, path.Join(subpath, "api")+"/")
}
func IsWebhookCall(a *app.App, r *http.Request) bool {
subpath, _ := utils.GetSubpathFromConfig(a.Config())
return strings.HasPrefix(r.URL.Path, path.Join(subpath, "hooks")+"/")
}
func ReturnStatusOK(w http.ResponseWriter) {