Merge branch 'master' into plugins-2
Этот коммит содержится в:
@@ -182,7 +182,6 @@ func New(options ...Option) (outApp *App, outErr error) {
|
||||
})
|
||||
|
||||
})
|
||||
app.regenerateClientConfig()
|
||||
|
||||
mlog.Info("Server is initializing...")
|
||||
|
||||
@@ -205,6 +204,9 @@ func New(options ...Option) (outApp *App, outErr error) {
|
||||
return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key")
|
||||
}
|
||||
|
||||
app.EnsureDiagnosticId()
|
||||
app.regenerateClientConfig()
|
||||
|
||||
app.initJobs()
|
||||
app.AddLicenseListener(func() {
|
||||
app.initJobs()
|
||||
|
||||
@@ -208,6 +208,9 @@ func (a *App) trackConfig() {
|
||||
"enable_user_access_tokens": *cfg.ServiceSettings.EnableUserAccessTokens,
|
||||
"enable_custom_emoji": *cfg.ServiceSettings.EnableCustomEmoji,
|
||||
"enable_emoji_picker": *cfg.ServiceSettings.EnableEmojiPicker,
|
||||
"enable_gif_picker": *cfg.ServiceSettings.EnableGifPicker,
|
||||
"gfycat_api_key": isDefault(*cfg.ServiceSettings.GfycatApiKey, model.SERVICE_SETTINGS_DEFAULT_GFYCAT_API_KEY),
|
||||
"gfycat_api_secret": isDefault(*cfg.ServiceSettings.GfycatApiSecret, model.SERVICE_SETTINGS_DEFAULT_GFYCAT_API_SECRET),
|
||||
"experimental_enable_authentication_transfer": *cfg.ServiceSettings.ExperimentalEnableAuthenticationTransfer,
|
||||
"restrict_custom_emoji_creation": *cfg.ServiceSettings.RestrictCustomEmojiCreation,
|
||||
"enable_testing": cfg.ServiceSettings.EnableTesting,
|
||||
|
||||
@@ -33,6 +33,7 @@ type LineImportData struct {
|
||||
Post *PostImportData `json:"post"`
|
||||
DirectChannel *DirectChannelImportData `json:"direct_channel"`
|
||||
DirectPost *DirectPostImportData `json:"direct_post"`
|
||||
Emoji *EmojiImportData `json:"emoji"`
|
||||
Version *int `json:"version"`
|
||||
}
|
||||
|
||||
@@ -114,6 +115,11 @@ type UserChannelNotifyPropsImportData struct {
|
||||
MarkUnread *string `json:"mark_unread"`
|
||||
}
|
||||
|
||||
type EmojiImportData struct {
|
||||
Name *string `json:"name"`
|
||||
Image *string `json:"image"`
|
||||
}
|
||||
|
||||
type ReactionImportData struct {
|
||||
User *string `json:"user"`
|
||||
CreateAt *int64 `json:"create_at"`
|
||||
@@ -337,6 +343,12 @@ func (a *App) ImportLine(line LineImportData, dryRun bool) *model.AppError {
|
||||
} else {
|
||||
return a.ImportDirectPost(line.DirectPost, dryRun)
|
||||
}
|
||||
case line.Type == "emoji":
|
||||
if line.Emoji == nil {
|
||||
return model.NewAppError("BulkImport", "app.import.import_line.null_emoji.error", nil, "", http.StatusBadRequest)
|
||||
} else {
|
||||
return a.ImportEmoji(line.Emoji, dryRun)
|
||||
}
|
||||
default:
|
||||
return model.NewAppError("BulkImport", "app.import.import_line.unknown_line_type.error", map[string]interface{}{"Type": line.Type}, "", http.StatusBadRequest)
|
||||
}
|
||||
@@ -1925,6 +1937,71 @@ func validateDirectPostImportData(data *DirectPostImportData, maxPostSize int) *
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ImportEmoji(data *EmojiImportData, dryRun bool) *model.AppError {
|
||||
if err := validateEmojiImportData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If this is a Dry Run, do not continue any further.
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
var emoji *model.Emoji
|
||||
var err *model.AppError
|
||||
|
||||
emoji, err = a.GetEmojiByName(*data.Name)
|
||||
if err != nil && err.StatusCode != http.StatusNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
alreadyExists := emoji != nil
|
||||
|
||||
if !alreadyExists {
|
||||
emoji = &model.Emoji{
|
||||
Name: *data.Name,
|
||||
}
|
||||
emoji.PreSave()
|
||||
}
|
||||
|
||||
file, fileErr := os.Open(*data.Image)
|
||||
if fileErr != nil {
|
||||
return model.NewAppError("BulkImport", "app.import.emoji.bad_file.error", map[string]interface{}{"EmojiName": *data.Name}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if _, err := a.WriteFile(file, getEmojiImagePath(emoji.Id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !alreadyExists {
|
||||
if result := <-a.Srv.Store.Emoji().Save(emoji); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateEmojiImportData(data *EmojiImportData) *model.AppError {
|
||||
if data == nil {
|
||||
return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.empty.error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if data.Name == nil || len(*data.Name) == 0 {
|
||||
return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.name_missing.error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := model.IsValidEmojiName(*data.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data.Image == nil || len(*data.Image) == 0 {
|
||||
return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.image_missing.error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// -- Old SlackImport Functions --
|
||||
// Import functions are sutible for entering posts and users into the database without
|
||||
|
||||
@@ -3774,11 +3774,16 @@ func TestImportBulkImport(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true })
|
||||
|
||||
teamName := model.NewId()
|
||||
channelName := model.NewId()
|
||||
username := model.NewId()
|
||||
username2 := model.NewId()
|
||||
username3 := model.NewId()
|
||||
emojiName := model.NewId()
|
||||
testsDir, _ := utils.FindDir("tests")
|
||||
testImage := filepath.Join(testsDir, "test.png")
|
||||
|
||||
// Run bulk import with a valid 1 of everything.
|
||||
data1 := `{"type": "version", "version": 1}
|
||||
@@ -3791,7 +3796,8 @@ func TestImportBulkImport(t *testing.T) {
|
||||
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `"]}}
|
||||
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `", "` + username3 + `"]}}
|
||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `"], "user": "` + username + `", "message": "Hello Direct Channel", "create_at": 123456789013}}
|
||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789014}}`
|
||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789014}}
|
||||
{"type": "emoji", "emoji": {"name": "` + emojiName + `", "image": "` + testImage + `"}}`
|
||||
|
||||
if err, line := th.App.BulkImport(strings.NewReader(data1), false, 2); err != nil || line != 0 {
|
||||
t.Fatalf("BulkImport should have succeeded: %v, %v", err.Error(), line)
|
||||
@@ -3833,3 +3839,75 @@ func TestImportProcessImportDataFileVersionLine(t *testing.T) {
|
||||
t.Fatalf("Expected error on invalid version line.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportValidateEmojiImportData(t *testing.T) {
|
||||
data := EmojiImportData{
|
||||
Name: ptrStr("parrot"),
|
||||
Image: ptrStr("/path/to/image"),
|
||||
}
|
||||
|
||||
err := validateEmojiImportData(&data)
|
||||
assert.Nil(t, err, "Validation should succeed")
|
||||
|
||||
*data.Name = "smiley"
|
||||
err = validateEmojiImportData(&data)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
*data.Name = ""
|
||||
err = validateEmojiImportData(&data)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
*data.Name = ""
|
||||
*data.Image = ""
|
||||
err = validateEmojiImportData(&data)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
*data.Image = "/path/to/image"
|
||||
data.Name = nil
|
||||
err = validateEmojiImportData(&data)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
data.Name = ptrStr("parrot")
|
||||
data.Image = nil
|
||||
err = validateEmojiImportData(&data)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestImportImportEmoji(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true })
|
||||
|
||||
testsDir, _ := utils.FindDir("tests")
|
||||
testImage := filepath.Join(testsDir, "test.png")
|
||||
|
||||
data := EmojiImportData{Name: ptrStr(model.NewId())}
|
||||
err := th.App.ImportEmoji(&data, true)
|
||||
assert.NotNil(t, err, "Invalid emoji should have failed dry run")
|
||||
|
||||
result := <-th.App.Srv.Store.Emoji().GetByName(*data.Name)
|
||||
assert.Nil(t, result.Data, "Emoji should not have been imported")
|
||||
|
||||
data.Image = ptrStr(testImage)
|
||||
err = th.App.ImportEmoji(&data, true)
|
||||
assert.Nil(t, err, "Valid emoji should have passed dry run")
|
||||
|
||||
data = EmojiImportData{Name: ptrStr(model.NewId())}
|
||||
err = th.App.ImportEmoji(&data, false)
|
||||
assert.NotNil(t, err, "Invalid emoji should have failed apply mode")
|
||||
|
||||
data.Image = ptrStr("non-existent-file")
|
||||
err = th.App.ImportEmoji(&data, false)
|
||||
assert.NotNil(t, err, "Emoji with bad image file should have failed apply mode")
|
||||
|
||||
data.Image = ptrStr(testImage)
|
||||
err = th.App.ImportEmoji(&data, false)
|
||||
assert.Nil(t, err, "Valid emoji should have succeeded apply mode")
|
||||
|
||||
result = <-th.App.Srv.Store.Emoji().GetByName(*data.Name)
|
||||
assert.NotNil(t, result.Data, "Emoji should have been imported")
|
||||
|
||||
err = th.App.ImportEmoji(&data, false)
|
||||
assert.Nil(t, err, "Second run should have succeeded apply mode")
|
||||
}
|
||||
|
||||
@@ -782,6 +782,11 @@ func (a *App) GetOpenGraphMetadata(requestURL string) *opengraph.OpenGraph {
|
||||
|
||||
makeOpenGraphURLsAbsolute(og, requestURL)
|
||||
|
||||
// The URL should be the link the user provided in their message, not a redirected one.
|
||||
if og.URL != "" {
|
||||
og.URL = requestURL
|
||||
}
|
||||
|
||||
return og
|
||||
}
|
||||
|
||||
|
||||
25
app/user.go
25
app/user.go
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/golang/freetype"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -696,12 +697,7 @@ func CreateProfileImage(username string, userId string, initialFont string) ([]b
|
||||
|
||||
initial := string(strings.ToUpper(username)[0])
|
||||
|
||||
fontDir, _ := utils.FindDir("fonts")
|
||||
fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont))
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
font, err := freetype.ParseFont(fontBytes)
|
||||
font, err := getFont(initialFont)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
@@ -719,7 +715,7 @@ func CreateProfileImage(username string, userId string, initialFont string) ([]b
|
||||
c.SetDst(dstImg)
|
||||
c.SetSrc(srcImg)
|
||||
|
||||
pt := freetype.Pt(IMAGE_PROFILE_PIXEL_DIMENSION/6, IMAGE_PROFILE_PIXEL_DIMENSION*2/3)
|
||||
pt := freetype.Pt(IMAGE_PROFILE_PIXEL_DIMENSION/5, IMAGE_PROFILE_PIXEL_DIMENSION*2/3)
|
||||
_, err = c.DrawString(initial, pt)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.initial.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
@@ -734,6 +730,21 @@ func CreateProfileImage(username string, userId string, initialFont string) ([]b
|
||||
}
|
||||
}
|
||||
|
||||
func getFont(initialFont string) (*truetype.Font, error) {
|
||||
// Some people have the old default font still set, so just treat that as if they're using the new default
|
||||
if initialFont == "luximbi.ttf" {
|
||||
initialFont = "nunito-bold.ttf"
|
||||
}
|
||||
|
||||
fontDir, _ := utils.FindDir("fonts")
|
||||
fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return freetype.ParseFont(fontBytes)
|
||||
}
|
||||
|
||||
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
|
||||
var img []byte
|
||||
readFailed := false
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestCreateOAuthUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateProfileImage(t *testing.T) {
|
||||
b, err := CreateProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba", "luximbi.ttf")
|
||||
b, err := CreateProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba", "nunito-bold.ttf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -587,6 +587,8 @@ func (a *App) HandleIncomingWebhook(hookId string, req *model.IncomingWebhookReq
|
||||
hook = result.Data.(*model.IncomingWebhook)
|
||||
}
|
||||
|
||||
uchan := a.Srv.Store.User().Get(hook.UserId)
|
||||
|
||||
if len(req.Props) == 0 {
|
||||
req.Props = make(model.StringInterface)
|
||||
}
|
||||
@@ -637,8 +639,15 @@ func (a *App) HandleIncomingWebhook(hookId string, req *model.IncomingWebhookReq
|
||||
return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.channel_locked.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
var user *model.User
|
||||
if result := <-uchan; result.Err != nil {
|
||||
return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.user.app_error", nil, "err="+result.Err.Message, http.StatusForbidden)
|
||||
} else {
|
||||
user = result.Data.(*model.User)
|
||||
}
|
||||
|
||||
if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
|
||||
channel.Name == model.DEFAULT_CHANNEL {
|
||||
channel.Name == model.DEFAULT_CHANNEL && !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) {
|
||||
return model.NewAppError("HandleIncomingWebhook", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user