4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-06-27 20:29:24 +00:00

Compare commits

..

36 Commits

Author SHA1 Message Date
Wim
d6ee55e35f Release v1.1.2 2017-09-09 17:06:40 +02:00
Wim
aef64eec32 Update changelog 2017-09-09 17:04:01 +02:00
Wim
c4193d5ccd Add 4.2 support (mattermost) 2017-09-09 17:00:26 +02:00
Wim
0c94186818 Add darwin-amd64 to nightly builds 2017-09-09 14:42:45 +02:00
Wim
9039720013 Send images when text is empty regression. (mattermost). Closes #254 2017-09-08 00:16:17 +02:00
Wim
a3470f8aec Send first message after connect (slack). Closes #252 2017-09-07 23:47:23 +02:00
Wim
01badde21d Add message debugging (gitter) 2017-09-07 20:35:12 +02:00
a37b232dd9 remove comment about useAPI in sample configuration (#251) 2017-09-04 15:16:58 +02:00
579ee48385 remove useAPI from sample configuration (#250) 2017-09-04 15:16:29 +02:00
Wim
dd985d1dad Fix sending direct messages with APIv4 2017-09-04 14:24:22 +02:00
Wim
d2caea70a2 Release v1.1.1 2017-09-04 13:43:30 +02:00
Wim
21143cf5ee Fix public links (mattermost) 2017-09-04 12:50:42 +02:00
Wim
dc2aed698d Release v1.1.0 2017-09-01 20:20:46 +02:00
Wim
37c350f19f Convert utf-8 back to charset (irc). #247 2017-08-30 20:59:54 +02:00
Wim
9e03fcf162 Fix private channel joining bug (mattermost). Closes #248 2017-08-30 14:01:17 +02:00
Wim
8d4521c1df Update changelog 2017-08-29 23:45:39 +02:00
Wim
9226252336 Replace mentions from other bridges. (slack). Closes #233 2017-08-29 23:34:50 +02:00
Wim
f4fb83e787 Use the detected charset (irc) 2017-08-29 21:35:36 +02:00
Wim
e7fcb25107 Add a charset option (irc). Closes #247 2017-08-29 21:31:03 +02:00
Wim
5a85258f74 Update travis to go 1.9 2017-08-29 20:34:32 +02:00
Wim
2f7df2df43 Do not add messages without ID to cache 2017-08-29 20:28:44 +02:00
Wim
ad3a753718 Remove debug message 2017-08-28 23:07:13 +02:00
Wim
e45c551880 Add support for editing messages. Remove ZWSP as loopcheck (gitter) 2017-08-28 23:07:12 +02:00
Wim
e59d338d4e Use github.com/42wim/go-gitter for now 2017-08-28 23:07:11 +02:00
Wim
7a86044f7a Add support for editing messages (telegram) 2017-08-28 23:07:03 +02:00
Wim
8b98f605bc Add support for editing messages (slack) 2017-08-28 20:29:02 +02:00
Wim
7c773ebae0 Add support for editing messages across bridges. Currently mattermost/discord.
Our Message type has an extra ID field which contains the message ID of the specific bridge.
The Send() function has been modified to return a msg ID (after the message to that specific
bridge has been created).

There is a lru cache of 5000 entries (message IDs). All in memory, so editing messages
will only work for messages the bot has seen.

Currently we go out from the idea that every message ID is unique, so we don't keep
the ID separate for each bridge. (we do for each gateway though)

If there's a new message from a bridge, we put that message ID in the LRU cache as key
and the []*BrMsgID as value (this slice contains the message ID's of each bridge that
received the new message)

If there's a new message and this message ID already exists in the cache, it must be
an updated message. The value from the cache gets checked for each bridge and if there
is a message ID for this bridge, the ID will be added to the Message{} sent to that
bridge. If the bridge sees that the ID isn't empty, it'll know it has to update the
message with that specific ID instead of creating a new message.
2017-08-28 00:33:17 +02:00
Wim
e84417430d Update PostMessage to also return and error. Add EditMessage function 2017-08-28 00:32:56 +02:00
Wim
5a8d7b5f6d Modify Send() to return also a message id 2017-08-27 22:59:37 +02:00
Wim
cfb8107138 Relay notices (matrix). Closes #243 2017-08-27 01:01:35 +02:00
Wim
43bd779fb7 Handle leave/join events (slack). Closes #246 2017-08-27 00:00:02 +02:00
Wim
7f9a400776 Add support for personal access tokens (mattermost)
* https://docs.mattermost.com/developer/personal-access-tokens.html
2017-08-23 22:49:42 +02:00
Wim
ce1c5873ac Make megacheck happy 2017-08-17 00:00:41 +02:00
Wim
85ff1995fd Use mattermost v4 api (drops support for mattermost < 3.8) 2017-08-16 23:41:35 +02:00
Wim
b963f83c6a Update mattermost vendor (3.7 => 4.1) 2017-08-16 23:37:37 +02:00
Wim
f6297ebbb0 Bump version 2017-08-16 23:28:11 +02:00
176 changed files with 10654 additions and 1433 deletions

View File

@ -1,7 +1,7 @@
language: go language: go
go: go:
#- 1.7.x #- 1.7.x
- 1.8.x - 1.9.x
# - tip # - tip
# we have everything vendored # we have everything vendored

View File

@ -36,7 +36,7 @@ Has a REST API.
# Requirements # Requirements
Accounts to one of the supported bridges Accounts to one of the supported bridges
* [Mattermost](https://github.com/mattermost/platform/) 3.5.x - 3.10.x, 4.0.x * [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.0.x - 4.2.x
* [IRC](http://www.mirc.com/servers.html) * [IRC](http://www.mirc.com/servers.html)
* [XMPP](https://jabber.org) * [XMPP](https://jabber.org)
* [Gitter](https://gitter.im) * [Gitter](https://gitter.im)
@ -53,7 +53,7 @@ See https://github.com/42wim/matterbridge/wiki
# Installing # Installing
## Binaries ## Binaries
* Latest stable release [v1.0.1](https://github.com/42wim/matterbridge/releases/latest) * Latest stable release [v1.1.2](https://github.com/42wim/matterbridge/releases/latest)
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/) * Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
## Building ## Building

View File

@ -66,11 +66,11 @@ func (b *Api) JoinChannel(channel config.ChannelInfo) error {
} }
func (b *Api) Send(msg config.Message) error { func (b *Api) Send(msg config.Message) (string, error) {
b.Lock() b.Lock()
defer b.Unlock() defer b.Unlock()
b.Messages.Enqueue(&msg) b.Messages.Enqueue(&msg)
return nil return "", nil
} }
func (b *Api) handlePostMessage(c echo.Context) error { func (b *Api) handlePostMessage(c echo.Context) error {

View File

@ -19,7 +19,7 @@ import (
) )
type Bridger interface { type Bridger interface {
Send(msg config.Message) error Send(msg config.Message) (string, error)
Connect() error Connect() error
JoinChannel(channel config.ChannelInfo) error JoinChannel(channel config.ChannelInfo) error
Disconnect() error Disconnect() error

View File

@ -27,6 +27,7 @@ type Message struct {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Gateway string `json:"gateway"` Gateway string `json:"gateway"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
ID string `json:"id"`
} }
type ChannelInfo struct { type ChannelInfo struct {
@ -42,6 +43,7 @@ type Protocol struct {
AuthCode string // steam AuthCode string // steam
BindAddress string // mattermost, slack // DEPRECATED BindAddress string // mattermost, slack // DEPRECATED
Buffer int // api Buffer int // api
Charset string // irc
EditSuffix string // mattermost, slack, discord, telegram, gitter EditSuffix string // mattermost, slack, discord, telegram, gitter
EditDisable bool // mattermost, slack, discord, telegram, gitter EditDisable bool // mattermost, slack, discord, telegram, gitter
IconURL string // mattermost, slack IconURL string // mattermost, slack

View File

@ -108,12 +108,12 @@ func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *bdiscord) Send(msg config.Message) error { func (b *bdiscord) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
channelID := b.getChannelID(msg.Channel) channelID := b.getChannelID(msg.Channel)
if channelID == "" { if channelID == "" {
flog.Errorf("Could not find channelID for %v", msg.Channel) flog.Errorf("Could not find channelID for %v", msg.Channel)
return nil return "", nil
} }
if msg.Event == config.EVENT_USER_ACTION { if msg.Event == config.EVENT_USER_ACTION {
msg.Text = "_" + msg.Text + "_" msg.Text = "_" + msg.Text + "_"
@ -129,20 +129,27 @@ func (b *bdiscord) Send(msg config.Message) error {
if wID == "" { if wID == "" {
flog.Debugf("Broadcasting using token (API)") flog.Debugf("Broadcasting using token (API)")
b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) if msg.ID != "" {
} else { _, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
flog.Debugf("Broadcasting using Webhook") return msg.ID, err
b.c.WebhookExecute( }
wID, res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
wToken, if err != nil {
true, return "", err
&discordgo.WebhookParams{ }
Content: msg.Text, return res.ID, err
Username: msg.Username,
AvatarURL: msg.Avatar,
})
} }
return nil flog.Debugf("Broadcasting using Webhook")
err := b.c.WebhookExecute(
wID,
wToken,
true,
&discordgo.WebhookParams{
Content: msg.Text,
Username: msg.Username,
AvatarURL: msg.Avatar,
})
return "", err
} }
func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
@ -185,7 +192,7 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
} }
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg",
UserID: m.Author.ID} UserID: m.Author.ID, ID: m.ID}
rmsg.Channel = b.getChannelName(m.ChannelID) rmsg.Channel = b.getChannelName(m.ChannelID)
if b.UseChannelID { if b.UseChannelID {

View File

@ -2,9 +2,9 @@ package bgitter
import ( import (
"fmt" "fmt"
"github.com/42wim/go-gitter"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/sromku/go-gitter"
"strings" "strings"
) )
@ -13,6 +13,7 @@ type Bgitter struct {
Config *config.Protocol Config *config.Protocol
Remote chan config.Message Remote chan config.Message
Account string Account string
User *gitter.User
Users []gitter.User Users []gitter.User
Rooms []gitter.Room Rooms []gitter.Room
} }
@ -36,7 +37,7 @@ func (b *Bgitter) Connect() error {
var err error var err error
flog.Info("Connecting") flog.Info("Connecting")
b.c = gitter.New(b.Config.Token) b.c = gitter.New(b.Config.Token)
_, err = b.c.GetUser() b.User, err = b.c.GetUser()
if err != nil { if err != nil {
flog.Debugf("%#v", err) flog.Debugf("%#v", err)
return err return err
@ -78,15 +79,16 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
for event := range stream.Event { for event := range stream.Event {
switch ev := event.Data.(type) { switch ev := event.Data.(type) {
case *gitter.MessageReceived: case *gitter.MessageReceived:
// check for ZWSP to see if it's not an echo if ev.Message.From.ID != b.User.ID {
if !strings.HasSuffix(ev.Message.Text, "") {
flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account) flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room,
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID} Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID,
ID: ev.Message.ID}
if strings.HasPrefix(ev.Message.Text, "@"+ev.Message.From.Username) { if strings.HasPrefix(ev.Message.Text, "@"+ev.Message.From.Username) {
rmsg.Event = config.EVENT_USER_ACTION rmsg.Event = config.EVENT_USER_ACTION
rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1) rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1)
} }
flog.Debugf("Message is %#v", rmsg)
b.Remote <- rmsg b.Remote <- rmsg
} }
case *gitter.GitterConnectionClosed: case *gitter.GitterConnectionClosed:
@ -97,15 +99,26 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Bgitter) Send(msg config.Message) error { func (b *Bgitter) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
roomID := b.getRoomID(msg.Channel) roomID := b.getRoomID(msg.Channel)
if roomID == "" { if roomID == "" {
flog.Errorf("Could not find roomID for %v", msg.Channel) flog.Errorf("Could not find roomID for %v", msg.Channel)
return nil return "", nil
} }
// add ZWSP because gitter echoes our own messages if msg.ID != "" {
return b.c.SendMessage(roomID, msg.Username+msg.Text+" ") flog.Debugf("updating message with id %s", msg.ID)
_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text)
if err != nil {
return "", err
}
return "", nil
}
resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
if err != nil {
return "", err
}
return resp.ID, nil
} }
func (b *Bgitter) getRoomID(channel string) string { func (b *Bgitter) getRoomID(channel string) string {

View File

@ -1,6 +1,7 @@
package birc package birc
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/42wim/go-ircevent" "github.com/42wim/go-ircevent"
@ -127,11 +128,24 @@ func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Birc) Send(msg config.Message) error { func (b *Birc) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
if strings.HasPrefix(msg.Text, "!") { if strings.HasPrefix(msg.Text, "!") {
b.Command(&msg) b.Command(&msg)
} }
if b.Config.Charset != "" {
buf := new(bytes.Buffer)
w, err := charset.NewWriter(b.Config.Charset, buf)
if err != nil {
flog.Errorf("charset from utf-8 conversion failed: %s", err)
return "", err
}
fmt.Fprintf(w, msg.Text)
w.Close()
msg.Text = buf.String()
}
for _, text := range strings.Split(msg.Text, "\n") { for _, text := range strings.Split(msg.Text, "\n") {
if len(text) > b.Config.MessageLength { if len(text) > b.Config.MessageLength {
text = text[:b.Config.MessageLength] + " <message clipped>" text = text[:b.Config.MessageLength] + " <message clipped>"
@ -145,7 +159,7 @@ func (b *Birc) Send(msg config.Message) error {
flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
} }
} }
return nil return "", nil
} }
func (b *Birc) doSend() { func (b *Birc) doSend() {
@ -265,20 +279,25 @@ func (b *Birc) handlePrivMsg(event *irc.Event) {
re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`) re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`)
msg = re.ReplaceAllString(msg, "") msg = re.ReplaceAllString(msg, "")
// detect what were sending so that we convert it to utf-8
detector := chardet.NewTextDetector()
result, err := detector.DetectBest([]byte(msg))
if err != nil {
flog.Infof("detection failed for msg: %#v", msg)
return
}
flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
var r io.Reader var r io.Reader
r, err = charset.NewReader(result.Charset, strings.NewReader(msg)) var err error
// if we're not sure, just pick ISO-8859-1 mycharset := b.Config.Charset
if result.Confidence < 80 { if mycharset == "" {
r, err = charset.NewReader("ISO-8859-1", strings.NewReader(msg)) // detect what were sending so that we convert it to utf-8
detector := chardet.NewTextDetector()
result, err := detector.DetectBest([]byte(msg))
if err != nil {
flog.Infof("detection failed for msg: %#v", msg)
return
}
flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
mycharset = result.Charset
// if we're not sure, just pick ISO-8859-1
if result.Confidence < 80 {
mycharset = "ISO-8859-1"
}
} }
r, err = charset.NewReader(mycharset, strings.NewReader(msg))
if err != nil { if err != nil {
flog.Errorf("charset to utf-8 conversion failed: %s", err) flog.Errorf("charset to utf-8 conversion failed: %s", err)
return return

View File

@ -74,17 +74,17 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
return err return err
} }
func (b *Bmatrix) Send(msg config.Message) error { func (b *Bmatrix) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
channel := b.getRoomID(msg.Channel) channel := b.getRoomID(msg.Channel)
flog.Debugf("Sending to channel %s", channel) flog.Debugf("Sending to channel %s", channel)
if msg.Event == config.EVENT_USER_ACTION { if msg.Event == config.EVENT_USER_ACTION {
b.mc.SendMessageEvent(channel, "m.room.message", b.mc.SendMessageEvent(channel, "m.room.message",
matrix.TextMessage{"m.emote", msg.Username + msg.Text}) matrix.TextMessage{"m.emote", msg.Username + msg.Text})
return nil return "", nil
} }
b.mc.SendText(channel, msg.Username+msg.Text) b.mc.SendText(channel, msg.Username+msg.Text)
return nil return "", nil
} }
func (b *Bmatrix) getRoomID(channel string) string { func (b *Bmatrix) getRoomID(channel string) string {
@ -100,7 +100,7 @@ func (b *Bmatrix) getRoomID(channel string) string {
func (b *Bmatrix) handlematrix() error { func (b *Bmatrix) handlematrix() error {
syncer := b.mc.Syncer.(*matrix.DefaultSyncer) syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
syncer.OnEventType("m.room.message", func(ev *matrix.Event) { syncer.OnEventType("m.room.message", func(ev *matrix.Event) {
if (ev.Content["msgtype"].(string) == "m.text" || ev.Content["msgtype"].(string) == "m.emote") && ev.Sender != b.UserID { if (ev.Content["msgtype"].(string) == "m.text" || ev.Content["msgtype"].(string) == "m.notice" || ev.Content["msgtype"].(string) == "m.emote") && ev.Sender != b.UserID {
b.RLock() b.RLock()
channel, ok := b.RoomMap[ev.RoomID] channel, ok := b.RoomMap[ev.RoomID]
b.RUnlock() b.RUnlock()

View File

@ -2,6 +2,7 @@ package bmattermost
import ( import (
"errors" "errors"
"fmt"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/matterclient" "github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook" "github.com/42wim/matterbridge/matterhook"
@ -23,6 +24,7 @@ type MMMessage struct {
Channel string Channel string
Username string Username string
UserID string UserID string
ID string
} }
type Bmattermost struct { type Bmattermost struct {
@ -61,6 +63,12 @@ func (b *Bmattermost) Connect() error {
b.mh = matterhook.New(b.Config.WebhookURL, b.mh = matterhook.New(b.Config.WebhookURL,
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
BindAddress: b.Config.WebhookBindAddress}) BindAddress: b.Config.WebhookBindAddress})
} else if b.Config.Token != "" {
flog.Info("Connecting using token (sending)")
err := b.apiLogin()
if err != nil {
return err
}
} else if b.Config.Login != "" { } else if b.Config.Login != "" {
flog.Info("Connecting using login/password (sending)") flog.Info("Connecting using login/password (sending)")
err := b.apiLogin() err := b.apiLogin()
@ -81,7 +89,14 @@ func (b *Bmattermost) Connect() error {
b.mh = matterhook.New(b.Config.WebhookURL, b.mh = matterhook.New(b.Config.WebhookURL,
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
DisableServer: true}) DisableServer: true})
if b.Config.Login != "" { if b.Config.Token != "" {
flog.Info("Connecting using token (receiving)")
err := b.apiLogin()
if err != nil {
return err
}
go b.handleMatter()
} else if b.Config.Login != "" {
flog.Info("Connecting using login/password (receiving)") flog.Info("Connecting using login/password (receiving)")
err := b.apiLogin() err := b.apiLogin()
if err != nil { if err != nil {
@ -90,6 +105,13 @@ func (b *Bmattermost) Connect() error {
go b.handleMatter() go b.handleMatter()
} }
return nil return nil
} else if b.Config.Token != "" {
flog.Info("Connecting using token (sending and receiving)")
err := b.apiLogin()
if err != nil {
return err
}
go b.handleMatter()
} else if b.Config.Login != "" { } else if b.Config.Login != "" {
flog.Info("Connecting using login/password (sending and receiving)") flog.Info("Connecting using login/password (sending and receiving)")
err := b.apiLogin() err := b.apiLogin()
@ -98,8 +120,8 @@ func (b *Bmattermost) Connect() error {
} }
go b.handleMatter() go b.handleMatter()
} }
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" { if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" && b.Config.Token == "" {
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Login/Password/Server/Team configured.") return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured.")
} }
return nil return nil
} }
@ -111,12 +133,16 @@ func (b *Bmattermost) Disconnect() error {
func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
// we can only join channels using the API // we can only join channels using the API
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" { if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
return b.mc.JoinChannel(b.mc.GetChannelId(channel.Name, "")) id := b.mc.GetChannelId(channel.Name, "")
if id == "" {
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
}
return b.mc.JoinChannel(id)
} }
return nil return nil
} }
func (b *Bmattermost) Send(msg config.Message) error { func (b *Bmattermost) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
if msg.Event == config.EVENT_USER_ACTION { if msg.Event == config.EVENT_USER_ACTION {
msg.Text = "*" + msg.Text + "*" msg.Text = "*" + msg.Text + "*"
@ -138,12 +164,14 @@ func (b *Bmattermost) Send(msg config.Message) error {
err := b.mh.Send(matterMessage) err := b.mh.Send(matterMessage)
if err != nil { if err != nil {
flog.Info(err) flog.Info(err)
return err return "", err
} }
return nil return "", nil
} }
b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message) if msg.ID != "" {
return nil return b.mc.EditMessage(msg.ID, message)
}
return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
} }
func (b *Bmattermost) handleMatter() { func (b *Bmattermost) handleMatter() {
@ -152,17 +180,22 @@ func (b *Bmattermost) handleMatter() {
flog.Debugf("Choosing webhooks based receiving") flog.Debugf("Choosing webhooks based receiving")
go b.handleMatterHook(mchan) go b.handleMatterHook(mchan)
} else { } else {
flog.Debugf("Choosing login/password based receiving") if b.Config.Token != "" {
flog.Debugf("Choosing token based receiving")
} else {
flog.Debugf("Choosing login/password based receiving")
}
go b.handleMatterClient(mchan) go b.handleMatterClient(mchan)
} }
for message := range mchan { for message := range mchan {
rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID} rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID}
text, ok := b.replaceAction(message.Text) text, ok := b.replaceAction(message.Text)
if ok { if ok {
rmsg.Event = config.EVENT_USER_ACTION rmsg.Event = config.EVENT_USER_ACTION
} }
rmsg.Text = text rmsg.Text = text
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
flog.Debugf("Message is %#v", rmsg)
b.Remote <- rmsg b.Remote <- rmsg
} }
} }
@ -194,6 +227,7 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
m.Username = message.Username m.Username = message.Username
m.Channel = message.Channel m.Channel = message.Channel
m.Text = message.Text m.Text = message.Text
m.ID = message.Post.Id
if message.Raw.Event == "post_edited" && !b.Config.EditDisable { if message.Raw.Event == "post_edited" && !b.Config.EditDisable {
m.Text = message.Text + b.Config.EditSuffix m.Text = message.Text + b.Config.EditSuffix
} }
@ -221,7 +255,12 @@ func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
} }
func (b *Bmattermost) apiLogin() error { func (b *Bmattermost) apiLogin() error {
b.mc = matterclient.New(b.Config.Login, b.Config.Password, password := b.Config.Password
if b.Config.Token != "" {
password = "MMAUTHTOKEN=" + b.Config.Token
}
b.mc = matterclient.New(b.Config.Login, password,
b.Config.Team, b.Config.Server) b.Config.Team, b.Config.Server)
b.mc.SkipTLSVerify = b.Config.SkipTLSVerify b.mc.SkipTLSVerify = b.Config.SkipTLSVerify
b.mc.NoTLS = b.Config.NoTLS b.mc.NoTLS = b.Config.NoTLS

View File

@ -57,7 +57,7 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Brocketchat) Send(msg config.Message) error { func (b *Brocketchat) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
matterMessage.Channel = msg.Channel matterMessage.Channel = msg.Channel
@ -67,9 +67,9 @@ func (b *Brocketchat) Send(msg config.Message) error {
err := b.mh.Send(matterMessage) err := b.mh.Send(matterMessage)
if err != nil { if err != nil {
flog.Info(err) flog.Info(err)
return err return "", err
} }
return nil return "", nil
} }
func (b *Brocketchat) handleRocketHook() { func (b *Brocketchat) handleRocketHook() {

View File

@ -125,7 +125,7 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Bslack) Send(msg config.Message) error { func (b *Bslack) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
if msg.Event == config.EVENT_USER_ACTION { if msg.Event == config.EVENT_USER_ACTION {
msg.Text = "_" + msg.Text + "_" msg.Text = "_" + msg.Text + "_"
@ -145,13 +145,13 @@ func (b *Bslack) Send(msg config.Message) error {
err := b.mh.Send(matterMessage) err := b.mh.Send(matterMessage)
if err != nil { if err != nil {
flog.Info(err) flog.Info(err)
return err return "", err
} }
return nil return "", nil
} }
schannel, err := b.getChannelByName(channel) schannel, err := b.getChannelByName(channel)
if err != nil { if err != nil {
return err return "", err
} }
np := slack.NewPostMessageParameters() np := slack.NewPostMessageParameters()
if b.Config.PrefixMessagesWithNick { if b.Config.PrefixMessagesWithNick {
@ -163,14 +163,20 @@ func (b *Bslack) Send(msg config.Message) error {
np.IconURL = msg.Avatar np.IconURL = msg.Avatar
} }
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"}) np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"})
b.sc.PostMessage(schannel.ID, message, np) // replace mentions
np.LinkNames = 1
/* // if we have no ID it means we're creating a new message, not updating an existing one
newmsg := b.rtm.NewOutgoingMessage(message, schannel.ID) if msg.ID != "" {
b.rtm.SendMessage(newmsg) ts := strings.Fields(msg.ID)
*/ b.sc.UpdateMessage(schannel.ID, ts[1], message)
return "", nil
return nil }
_, id, err := b.sc.PostMessage(schannel.ID, message, np)
if err != nil {
return "", err
}
return "slack " + id, nil
} }
func (b *Bslack) getAvatar(user string) string { func (b *Bslack) getAvatar(user string) string {
@ -233,75 +239,78 @@ func (b *Bslack) handleSlack() {
text = b.replaceURL(text) text = b.replaceURL(text)
text = html.UnescapeString(text) text = html.UnescapeString(text)
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID} msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID, ID: "slack " + message.Raw.Timestamp}
if message.Raw.SubType == "me_message" { if message.Raw.SubType == "me_message" {
msg.Event = config.EVENT_USER_ACTION msg.Event = config.EVENT_USER_ACTION
} }
if message.Raw.SubType == "channel_leave" || message.Raw.SubType == "channel_join" {
msg.Username = "system"
msg.Event = config.EVENT_JOIN_LEAVE
}
// edited messages have a submessage, use this timestamp
if message.Raw.SubMessage != nil {
msg.ID = "slack " + message.Raw.SubMessage.Timestamp
}
b.Remote <- msg b.Remote <- msg
} }
} }
} }
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
count := 0
for msg := range b.rtm.IncomingEvents { for msg := range b.rtm.IncomingEvents {
switch ev := msg.Data.(type) { switch ev := msg.Data.(type) {
case *slack.MessageEvent: case *slack.MessageEvent:
// ignore first message flog.Debugf("Receiving from slackclient %#v", ev)
if count > 0 { if len(ev.Attachments) > 0 {
flog.Debugf("Receiving from slackclient %#v", ev) // skip messages we made ourselves
if len(ev.Attachments) > 0 { if ev.Attachments[0].CallbackID == "matterbridge" {
// skip messages we made ourselves continue
if ev.Attachments[0].CallbackID == "matterbridge" {
continue
}
} }
if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp { }
flog.Debugf("SubMessage %#v", ev.SubMessage) if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
ev.User = ev.SubMessage.User flog.Debugf("SubMessage %#v", ev.SubMessage)
ev.Text = ev.SubMessage.Text + b.Config.EditSuffix ev.User = ev.SubMessage.User
} ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
// use our own func because rtm.GetChannelInfo doesn't work for private channels }
channel, err := b.getChannelByID(ev.Channel) // use our own func because rtm.GetChannelInfo doesn't work for private channels
channel, err := b.getChannelByID(ev.Channel)
if err != nil {
continue
}
m := &MMMessage{}
if ev.BotID == "" {
user, err := b.rtm.GetUserInfo(ev.User)
if err != nil { if err != nil {
continue continue
} }
m := &MMMessage{} m.UserID = user.ID
if ev.BotID == "" { m.Username = user.Name
user, err := b.rtm.GetUserInfo(ev.User)
if err != nil {
continue
}
m.UserID = user.ID
m.Username = user.Name
}
m.Channel = channel.Name
m.Text = ev.Text
if m.Text == "" {
for _, attach := range ev.Attachments {
if attach.Text != "" {
m.Text = attach.Text
} else {
m.Text = attach.Fallback
}
}
}
m.Raw = ev
m.Text = b.replaceMention(m.Text)
// when using webhookURL we can't check if it's our webhook or not for now
if ev.BotID != "" && b.Config.WebhookURL == "" {
bot, err := b.rtm.GetBotInfo(ev.BotID)
if err != nil {
continue
}
if bot.Name != "" {
m.Username = bot.Name
m.UserID = bot.ID
}
}
mchan <- m
} }
count++ m.Channel = channel.Name
m.Text = ev.Text
if m.Text == "" {
for _, attach := range ev.Attachments {
if attach.Text != "" {
m.Text = attach.Text
} else {
m.Text = attach.Fallback
}
}
}
m.Raw = ev
m.Text = b.replaceMention(m.Text)
// when using webhookURL we can't check if it's our webhook or not for now
if ev.BotID != "" && b.Config.WebhookURL == "" {
bot, err := b.rtm.GetBotInfo(ev.BotID)
if err != nil {
continue
}
if bot.Name != "" {
m.Username = bot.Name
m.UserID = bot.ID
}
}
mchan <- m
case *slack.OutgoingErrorEvent: case *slack.OutgoingErrorEvent:
flog.Debugf("%#v", ev.Error()) flog.Debugf("%#v", ev.Error())
case *slack.ChannelJoinedEvent: case *slack.ChannelJoinedEvent:

View File

@ -69,13 +69,13 @@ func (b *Bsteam) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Bsteam) Send(msg config.Message) error { func (b *Bsteam) Send(msg config.Message) (string, error) {
id, err := steamid.NewId(msg.Channel) id, err := steamid.NewId(msg.Channel)
if err != nil { if err != nil {
return err return "", err
} }
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
return nil return "", nil
} }
func (b *Bsteam) getNick(id steamid.SteamId) string { func (b *Bsteam) getNick(id steamid.SteamId) string {

View File

@ -57,22 +57,41 @@ func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Btelegram) Send(msg config.Message) error { func (b *Btelegram) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
chatid, err := strconv.ParseInt(msg.Channel, 10, 64) chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
if err != nil { if err != nil {
return err return "", err
} }
if b.Config.MessageFormat == "HTML" { if b.Config.MessageFormat == "HTML" {
msg.Text = makeHTML(msg.Text) msg.Text = makeHTML(msg.Text)
} }
// edit the message if we have a msg ID
if msg.ID != "" {
msgid, err := strconv.Atoi(msg.ID)
if err != nil {
return "", err
}
m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
_, err = b.c.Send(m)
if err != nil {
return "", err
}
return "", nil
}
m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text) m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
if b.Config.MessageFormat == "HTML" { if b.Config.MessageFormat == "HTML" {
m.ParseMode = tgbotapi.ModeHTML m.ParseMode = tgbotapi.ModeHTML
} }
_, err = b.c.Send(m) res, err := b.c.Send(m)
return err if err != nil {
return "", err
}
return strconv.Itoa(res.MessageID), nil
} }
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
@ -131,7 +150,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
} }
if text != "" { if text != "" {
flog.Debugf("Sending message from %s on %s to gateway", username, b.Account) flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID)} b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
} }
} }
} }

View File

@ -79,10 +79,10 @@ func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
return nil return nil
} }
func (b *Bxmpp) Send(msg config.Message) error { func (b *Bxmpp) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
return nil return "", nil
} }
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {

View File

@ -1,10 +1,34 @@
# v1.1.2
* general: also build darwin binaries
* mattermost: add support for mattermost 4.2.x
* mattermost: Send images when text is empty regression. (mattermost). Closes #254
* slack: also send the first messsage after connect. #252
# v1.1.1
## Bugfix
* mattermost: fix public links
# v1.1.0
## New features
* general: Add better editing support. (actually edit the messages on bridges that support it)
(mattermost,discord,gitter,slack,telegram)
* mattermost: use API v4 (removes support for mattermost < 3.8)
* mattermost: add support for personal access tokens (since mattermost 4.1)
Use ```Token="yourtoken"``` in mattermost config
See https://docs.mattermost.com/developer/personal-access-tokens.html for more info
* matrix: Relay notices (matrix). Closes #243
* irc: Add a charset option. Closes #247
## Bugfix
* slack: Handle leave/join events (slack). Closes #246
* slack: Replace mentions from other bridges. (slack). Closes #233
* gitter: remove ZWSP after messages
# v1.0.1 # v1.0.1
## New features ## New features
* mattermost: add support for mattermost 4.1.x * mattermost: add support for mattermost 4.1.x
* discord: allow a webhookURL per channel #239 * discord: allow a webhookURL per channel #239
## Bugfix
# v1.0.0 # v1.0.0
## New features ## New features
* general: Add action support for slack,mattermost,irc,gitter,matrix,xmpp,discord. #199 * general: Add action support for slack,mattermost,irc,gitter,matrix,xmpp,discord. #199

View File

@ -1,10 +1,11 @@
#!/bin/bash #!/bin/bash
go version |grep go1.8 || exit go version |grep go1.9 || exit
VERSION=$(git describe --tags) VERSION=$(git describe --tags)
mkdir ci/binaries mkdir ci/binaries
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-win64.exe GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux64 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-amd64
GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-arm GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-arm
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-darwin-amd64
cd ci cd ci
cat > deploy.json <<EOF cat > deploy.json <<EOF
{ {

View File

@ -6,6 +6,7 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
// "github.com/davecgh/go-spew/spew" // "github.com/davecgh/go-spew/spew"
"github.com/hashicorp/golang-lru"
"github.com/peterhellberg/emojilib" "github.com/peterhellberg/emojilib"
"regexp" "regexp"
"strings" "strings"
@ -21,11 +22,19 @@ type Gateway struct {
ChannelOptions map[string]config.ChannelOptions ChannelOptions map[string]config.ChannelOptions
Message chan config.Message Message chan config.Message
Name string Name string
Messages *lru.Cache
}
type BrMsgID struct {
br *bridge.Bridge
ID string
} }
func New(cfg config.Gateway, r *Router) *Gateway { func New(cfg config.Gateway, r *Router) *Gateway {
gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message, gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message,
Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config} Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config}
cache, _ := lru.New(5000)
gw.Messages = cache
gw.AddConfig(&cfg) gw.AddConfig(&cfg)
return gw return gw
} }
@ -136,15 +145,16 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
return channels return channels
} }
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) { func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
var brMsgIDs []*BrMsgID
// only relay join/part when configged // only relay join/part when configged
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
return return brMsgIDs
} }
// broadcast to every out channel (irc QUIT) // broadcast to every out channel (irc QUIT)
if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE { if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE {
log.Debug("empty channel") log.Debug("empty channel")
return return brMsgIDs
} }
originchannel := msg.Channel originchannel := msg.Channel
origmsg := msg origmsg := msg
@ -158,15 +168,29 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
msg.Channel = channel.Name msg.Channel = channel.Name
msg.Avatar = gw.modifyAvatar(origmsg, dest) msg.Avatar = gw.modifyAvatar(origmsg, dest)
msg.Username = gw.modifyUsername(origmsg, dest) msg.Username = gw.modifyUsername(origmsg, dest)
msg.ID = ""
if res, ok := gw.Messages.Get(origmsg.ID); ok {
IDs := res.([]*BrMsgID)
for _, id := range IDs {
if dest.Protocol == id.br.Protocol {
msg.ID = id.ID
}
}
}
// for api we need originchannel as channel // for api we need originchannel as channel
if dest.Protocol == "api" { if dest.Protocol == "api" {
msg.Channel = originchannel msg.Channel = originchannel
} }
err := dest.Send(msg) mID, err := dest.Send(msg)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
if mID != "" {
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID})
}
} }
return brMsgIDs
} }
func (gw *Gateway) ignoreMessage(msg *config.Message) bool { func (gw *Gateway) ignoreMessage(msg *config.Message) bool {

View File

@ -94,11 +94,17 @@ func (r *Router) handleReceive() {
} }
} }
for _, gw := range r.Gateways { for _, gw := range r.Gateways {
// record all the message ID's of the different bridges
var msgIDs []*BrMsgID
if !gw.ignoreMessage(&msg) { if !gw.ignoreMessage(&msg) {
msg.Timestamp = time.Now() msg.Timestamp = time.Now()
gw.modifyMessage(&msg) gw.modifyMessage(&msg)
for _, br := range gw.Bridges { for _, br := range gw.Bridges {
gw.handleMessage(msg, br) msgIDs = append(msgIDs, gw.handleMessage(msg, br)...)
}
// only add the message ID if it doesn't already exists
if _, ok := gw.Messages.Get(msg.ID); !ok && msg.ID != "" {
gw.Messages.Add(msg.ID, msgIDs)
} }
} }
} }

View File

@ -11,7 +11,7 @@ import (
) )
var ( var (
version = "1.0.1" version = "1.1.2"
githash string githash string
) )

View File

@ -32,6 +32,23 @@ UseSASL=false
#OPTIONAL (default false) #OPTIONAL (default false)
SkipTLSVerify=true SkipTLSVerify=true
#If you know your charset, you can specify it manually.
#Otherwise it tries to detect this automatically. Select one below
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
# "cp932", "iso-8859-15", "cp437", "utf-16be", "iso-8859-3:1988", "windows-1251", "utf16", "latin6",
# "latin3", "iso-8859-1:1987", "iso-8859-9", "utf-16le", "big5", "cp819", "asmo-708", "utf-8",
# "ibm437", "iso-ir-157", "iso-ir-144", "latin4", "850", "iso-8859-5", "iso-8859-5:1988", "l3",
# "windows-31j", "utf8", "iso-8859-3", "437", "greek", "iso-8859-8", "l6", "l9-iso-8859-15",
# "iso-8859-2", "latin2", "iso-ir-100", "iso-8859-6", "arabic", "iso-ir-148", "us-ascii", "x-sjis",
# "utf16be", "iso-8859-8:1988", "utf16le", "l4", "utf-16", "iso-ir-138", "iso-8859-7", "iso-8859-7:1987",
# "windows-1252", "l2", "koi8-r", "iso8859-1", "latin1", "ecma-114", "iso-ir-110", "elot-928",
# "iso-ir-126", "iso-8859-1", "iso-ir-127", "cp850", "cyrillic", "greek8", "windows-1250", "iso-latin-1",
# "l5", "ibm866", "cp866", "ms-kanji", "ibm850", "ecma-118", "iso-ir-101", "ibm819", "l1", "iso-8859-6:1987",
# "latin5", "ascii", "sjis", "iso-8859-10", "iso-8859-4", "iso-8859-4:1988", "shift-jis
# The select charset will be converted to utf-8 when sent to other bridges.
#OPTIONAL (default "")
Charset=""
#Your nick on irc. #Your nick on irc.
#REQUIRED #REQUIRED
Nick="matterbot" Nick="matterbot"
@ -213,6 +230,11 @@ Team="yourteam"
Login="yourlogin" Login="yourlogin"
Password="yourpass" Password="yourpass"
#personal access token of the bot.
#new feature since mattermost 4.1. See https://docs.mattermost.com/developer/personal-access-tokens.html
#OPTIONAL (you can use token instead of login/password)
#Token="abcdefghijklm"
#Enable this to make a http connection (instead of https) to your mattermost. #Enable this to make a http connection (instead of https) to your mattermost.
#OPTIONAL (default false) #OPTIONAL (default false)
NoTLS=false NoTLS=false
@ -361,7 +383,6 @@ WebhookURL="https://hooks.slack.com/services/yourhook"
#AND DEDICATED BOT USER WHEN POSSIBLE! #AND DEDICATED BOT USER WHEN POSSIBLE!
#Address to listen on for outgoing webhook requests from slack #Address to listen on for outgoing webhook requests from slack
#See account settings - integrations - outgoing webhooks on slack #See account settings - integrations - outgoing webhooks on slack
#This setting will not be used when useAPI is eanbled
#webhooks #webhooks
#OPTIONAL #OPTIONAL
WebhookBindAddress="0.0.0.0:9999" WebhookBindAddress="0.0.0.0:9999"

View File

@ -6,7 +6,6 @@
[mattermost] [mattermost]
[mattermost.work] [mattermost.work]
useAPI=true
#do not prefix it wit http:// or https:// #do not prefix it wit http:// or https://
Server="yourmattermostserver.domain" Server="yourmattermostserver.domain"
Team="yourteam" Team="yourteam"

View File

@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -45,8 +44,8 @@ type Message struct {
type Team struct { type Team struct {
Team *model.Team Team *model.Team
Id string Id string
Channels *model.ChannelList Channels []*model.Channel
MoreChannels *model.ChannelList MoreChannels []*model.Channel
Users map[string]*model.User Users map[string]*model.User
} }
@ -55,7 +54,7 @@ type MMClient struct {
*Credentials *Credentials
Team *Team Team *Team
OtherTeams []*Team OtherTeams []*Team
Client *model.Client Client *model.Client4
User *model.User User *model.User
Users map[string]*model.User Users map[string]*model.User
MessageChan chan *Message MessageChan chan *Message
@ -109,21 +108,21 @@ func (m *MMClient) Login() error {
uriScheme = "http://" uriScheme = "http://"
} }
// login to mattermost // login to mattermost
m.Client = model.NewClient(uriScheme + m.Credentials.Server) m.Client = model.NewAPIv4Client(uriScheme + m.Credentials.Server)
m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}, Proxy: http.ProxyFromEnvironment} m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}, Proxy: http.ProxyFromEnvironment}
m.Client.HttpClient.Timeout = time.Second * 10 m.Client.HttpClient.Timeout = time.Second * 10
for { for {
d := b.Duration() d := b.Duration()
// bogus call to get the serverversion // bogus call to get the serverversion
_, err := m.Client.GetClientProperties() _, resp := m.Client.Logout()
if err != nil { if resp.Error != nil {
return fmt.Errorf("%#v", err.Error()) return fmt.Errorf("%#v", resp.Error.Error())
} }
if firstConnection && !supportedVersion(m.Client.ServerVersion) { if firstConnection && !supportedVersion(resp.ServerVersion) {
return fmt.Errorf("unsupported mattermost version: %s", m.Client.ServerVersion) return fmt.Errorf("unsupported mattermost version: %s", resp.ServerVersion)
} }
m.ServerVersion = m.Client.ServerVersion m.ServerVersion = resp.ServerVersion
if m.ServerVersion == "" { if m.ServerVersion == "" {
m.log.Debugf("Server not up yet, reconnecting in %s", d) m.log.Debugf("Server not up yet, reconnecting in %s", d)
time.Sleep(d) time.Sleep(d)
@ -134,30 +133,33 @@ func (m *MMClient) Login() error {
} }
b.Reset() b.Reset()
var myinfo *model.Result var resp *model.Response
//var myinfo *model.Result
var appErr *model.AppError var appErr *model.AppError
var logmsg = "trying login" var logmsg = "trying login"
for { for {
m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) {
m.log.Debugf(logmsg+" with %s", model.SESSION_COOKIE_TOKEN) m.log.Debugf(logmsg + " with token")
token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=")
if len(token) != 2 { if len(token) != 2 {
return errors.New("incorrect MMAUTHTOKEN. valid input is MMAUTHTOKEN=yourtoken") return errors.New("incorrect MMAUTHTOKEN. valid input is MMAUTHTOKEN=yourtoken")
} }
m.Client.HttpClient.Jar = m.createCookieJar(token[1]) m.Client.HttpClient.Jar = m.createCookieJar(token[1])
m.Client.MockSession(token[1]) m.Client.AuthToken = token[1]
myinfo, appErr = m.Client.GetMe("") m.Client.AuthType = model.HEADER_BEARER
if appErr != nil { m.User, resp = m.Client.GetMe("")
return errors.New(appErr.DetailedError) if resp.Error != nil {
return resp.Error
} }
if myinfo.Data.(*model.User) == nil { if m.User == nil {
m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) return errors.New("invalid " + model.SESSION_COOKIE_TOKEN)
} }
} else { } else {
_, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) m.User, resp = m.Client.Login(m.Credentials.Login, m.Credentials.Pass)
} }
appErr = resp.Error
if appErr != nil { if appErr != nil {
d := b.Duration() d := b.Duration()
m.log.Debug(appErr.DetailedError) m.log.Debug(appErr.DetailedError)
@ -185,8 +187,6 @@ func (m *MMClient) Login() error {
if m.Team == nil { if m.Team == nil {
return errors.New("team not found") return errors.New("team not found")
} }
// set our team id as default route
m.Client.SetTeamId(m.Team.Id)
m.wsConnect() m.wsConnect()
@ -207,7 +207,7 @@ func (m *MMClient) wsConnect() {
} }
// setup websocket connection // setup websocket connection
wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX_V3 + "/users/websocket" wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX_V4 + "/websocket"
header := http.Header{} header := http.Header{}
header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken)
@ -241,9 +241,9 @@ func (m *MMClient) Logout() error {
m.log.Debug("Not invalidating session in logout, credential is a token") m.log.Debug("Not invalidating session in logout, credential is a token")
return nil return nil
} }
_, err := m.Client.Logout() _, resp := m.Client.Logout()
if err != nil { if resp.Error != nil {
return err return resp.Error
} }
return nil return nil
} }
@ -277,6 +277,13 @@ func (m *MMClient) WsReceiver() {
// check if we didn't empty the message // check if we didn't empty the message
if msg.Text != "" { if msg.Text != "" {
m.MessageChan <- msg m.MessageChan <- msg
continue
}
// if we have file attached but the message is empty, also send it
if msg.Post != nil {
if msg.Text != "" || len(msg.Post.FileIds) > 0 {
m.MessageChan <- msg
}
} }
continue continue
} }
@ -349,33 +356,34 @@ func (m *MMClient) parseActionPost(rmsg *Message) {
} }
func (m *MMClient) UpdateUsers() error { func (m *MMClient) UpdateUsers() error {
mmusers, err := m.Client.GetProfiles(0, 50000, "") mmusers, resp := m.Client.GetUsers(0, 50000, "")
if err != nil { if resp.Error != nil {
return errors.New(err.DetailedError) return errors.New(resp.Error.DetailedError)
} }
m.Lock() m.Lock()
m.Users = mmusers.Data.(map[string]*model.User) for _, user := range mmusers {
m.Users[user.Id] = user
}
m.Unlock() m.Unlock()
return nil return nil
} }
func (m *MMClient) UpdateChannels() error { func (m *MMClient) UpdateChannels() error {
mmchannels, err := m.Client.GetChannels("") mmchannels, resp := m.Client.GetChannelsForTeamForUser(m.Team.Id, m.User.Id, "")
if err != nil { if resp.Error != nil {
return errors.New(err.DetailedError) return errors.New(resp.Error.DetailedError)
}
var mmchannels2 *model.Result
if m.mmVersion() >= 3.08 {
mmchannels2, err = m.Client.GetMoreChannelsPage(0, 5000)
} else {
mmchannels2, err = m.Client.GetMoreChannels("")
}
if err != nil {
return errors.New(err.DetailedError)
} }
m.Lock() m.Lock()
m.Team.Channels = mmchannels.Data.(*model.ChannelList) m.Team.Channels = mmchannels
m.Team.MoreChannels = mmchannels2.Data.(*model.ChannelList) m.Unlock()
mmchannels, resp = m.Client.GetPublicChannelsForTeam(m.Team.Id, 0, 5000, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
}
m.Lock()
m.Team.MoreChannels = mmchannels
m.Unlock() m.Unlock()
return nil return nil
} }
@ -388,14 +396,14 @@ func (m *MMClient) GetChannelName(channelId string) string {
continue continue
} }
if t.Channels != nil { if t.Channels != nil {
for _, channel := range *t.Channels { for _, channel := range t.Channels {
if channel.Id == channelId { if channel.Id == channelId {
return channel.Name return channel.Name
} }
} }
} }
if t.MoreChannels != nil { if t.MoreChannels != nil {
for _, channel := range *t.MoreChannels { for _, channel := range t.MoreChannels {
if channel.Id == channelId { if channel.Id == channelId {
return channel.Name return channel.Name
} }
@ -413,7 +421,7 @@ func (m *MMClient) GetChannelId(name string, teamId string) string {
} }
for _, t := range m.OtherTeams { for _, t := range m.OtherTeams {
if t.Id == teamId { if t.Id == teamId {
for _, channel := range append(*t.Channels, *t.MoreChannels...) { for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Name == name { if channel.Name == name {
return channel.Id return channel.Id
} }
@ -427,7 +435,7 @@ func (m *MMClient) GetChannelTeamId(id string) string {
m.RLock() m.RLock()
defer m.RUnlock() defer m.RUnlock()
for _, t := range append(m.OtherTeams, m.Team) { for _, t := range append(m.OtherTeams, m.Team) {
for _, channel := range append(*t.Channels, *t.MoreChannels...) { for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Id == id { if channel.Id == id {
return channel.TeamId return channel.TeamId
} }
@ -440,7 +448,7 @@ func (m *MMClient) GetChannelHeader(channelId string) string {
m.RLock() m.RLock()
defer m.RUnlock() defer m.RUnlock()
for _, t := range m.OtherTeams { for _, t := range m.OtherTeams {
for _, channel := range append(*t.Channels, *t.MoreChannels...) { for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Id == channelId { if channel.Id == channelId {
return channel.Header return channel.Header
} }
@ -450,55 +458,68 @@ func (m *MMClient) GetChannelHeader(channelId string) string {
return "" return ""
} }
func (m *MMClient) PostMessage(channelId string, text string) { func (m *MMClient) PostMessage(channelId string, text string) (string, error) {
post := &model.Post{ChannelId: channelId, Message: text} post := &model.Post{ChannelId: channelId, Message: text}
m.Client.CreatePost(post) res, resp := m.Client.CreatePost(post)
if resp.Error != nil {
return "", resp.Error
}
return res.Id, nil
}
func (m *MMClient) EditMessage(postId string, text string) (string, error) {
post := &model.Post{Message: text}
res, resp := m.Client.UpdatePost(postId, post)
if resp.Error != nil {
return "", resp.Error
}
return res.Id, nil
} }
func (m *MMClient) JoinChannel(channelId string) error { func (m *MMClient) JoinChannel(channelId string) error {
m.RLock() m.RLock()
defer m.RUnlock() defer m.RUnlock()
for _, c := range *m.Team.Channels { for _, c := range m.Team.Channels {
if c.Id == channelId { if c.Id == channelId {
m.log.Debug("Not joining ", channelId, " already joined.") m.log.Debug("Not joining ", channelId, " already joined.")
return nil return nil
} }
} }
m.log.Debug("Joining ", channelId) m.log.Debug("Joining ", channelId)
_, err := m.Client.JoinChannel(channelId) _, resp := m.Client.AddChannelMember(channelId, m.User.Id)
if err != nil { if resp.Error != nil {
return errors.New("failed to join") return resp.Error
} }
return nil return nil
} }
func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList { func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList {
res, err := m.Client.GetPostsSince(channelId, time) res, resp := m.Client.GetPostsSince(channelId, time)
if err != nil { if resp.Error != nil {
return nil return nil
} }
return res.Data.(*model.PostList) return res
} }
func (m *MMClient) SearchPosts(query string) *model.PostList { func (m *MMClient) SearchPosts(query string) *model.PostList {
res, err := m.Client.SearchPosts(query, false) res, resp := m.Client.SearchPosts(m.Team.Id, query, false)
if err != nil { if resp.Error != nil {
return nil return nil
} }
return res.Data.(*model.PostList) return res
} }
func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList {
res, err := m.Client.GetPosts(channelId, 0, limit, "") res, resp := m.Client.GetPostsForChannel(channelId, 0, limit, "")
if err != nil { if resp.Error != nil {
return nil return nil
} }
return res.Data.(*model.PostList) return res
} }
func (m *MMClient) GetPublicLink(filename string) string { func (m *MMClient) GetPublicLink(filename string) string {
res, err := m.Client.GetPublicLink(filename) res, resp := m.Client.GetFileLink(filename)
if err != nil { if resp.Error != nil {
return "" return ""
} }
return res return res
@ -507,8 +528,8 @@ func (m *MMClient) GetPublicLink(filename string) string {
func (m *MMClient) GetPublicLinks(filenames []string) []string { func (m *MMClient) GetPublicLinks(filenames []string) []string {
var output []string var output []string
for _, f := range filenames { for _, f := range filenames {
res, err := m.Client.GetPublicLink(f) res, resp := m.Client.GetFileLink(f)
if err != nil { if resp.Error != nil {
continue continue
} }
output = append(output, res) output = append(output, res)
@ -524,8 +545,8 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
var output []string var output []string
for _, f := range filenames { for _, f := range filenames {
res, err := m.Client.GetPublicLink(f) res, resp := m.Client.GetFileLink(f)
if err != nil { if resp.Error != nil {
// public links is probably disabled, create the link ourselves // public links is probably disabled, create the link ourselves
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get") output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get")
continue continue
@ -536,42 +557,33 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
} }
func (m *MMClient) UpdateChannelHeader(channelId string, header string) { func (m *MMClient) UpdateChannelHeader(channelId string, header string) {
data := make(map[string]string) channel := &model.Channel{Id: channelId, Header: header}
data["channel_id"] = channelId
data["channel_header"] = header
m.log.Debugf("updating channelheader %#v, %#v", channelId, header) m.log.Debugf("updating channelheader %#v, %#v", channelId, header)
_, err := m.Client.UpdateChannelHeader(data) _, resp := m.Client.UpdateChannel(channel)
if err != nil { if resp.Error != nil {
log.Error(err) log.Error(resp.Error)
} }
} }
func (m *MMClient) UpdateLastViewed(channelId string) { func (m *MMClient) UpdateLastViewed(channelId string) {
m.log.Debugf("posting lastview %#v", channelId) m.log.Debugf("posting lastview %#v", channelId)
if m.mmVersion() >= 3.08 { view := &model.ChannelView{ChannelId: channelId}
view := model.ChannelView{ChannelId: channelId} res, _ := m.Client.ViewChannel(m.User.Id, view)
res, _ := m.Client.ViewChannel(view) if !res {
if !res { m.log.Errorf("ChannelView update for %s failed", channelId)
m.log.Errorf("ChannelView update for %s failed", channelId)
}
return
}
_, err := m.Client.UpdateLastViewedAt(channelId, true)
if err != nil {
m.log.Error(err)
} }
} }
func (m *MMClient) UsernamesInChannel(channelId string) []string { func (m *MMClient) UsernamesInChannel(channelId string) []string {
res, err := m.Client.GetProfilesInChannel(channelId, 0, 50000, "") res, resp := m.Client.GetChannelMembers(channelId, 0, 50000, "")
if err != nil { if resp.Error != nil {
m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err) m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, resp.Error)
return []string{} return []string{}
} }
members := res.Data.(map[string]*model.User) allusers := m.GetUsers()
result := []string{} result := []string{}
for _, member := range members { for _, member := range *res {
result = append(result, member.Nickname) result = append(result, allusers[member.UserId].Nickname)
} }
return result return result
} }
@ -595,22 +607,15 @@ func (m *MMClient) createCookieJar(token string) *cookiejar.Jar {
func (m *MMClient) SendDirectMessage(toUserId string, msg string) { func (m *MMClient) SendDirectMessage(toUserId string, msg string) {
m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg)
// create DM channel (only happens on first message) // create DM channel (only happens on first message)
_, err := m.Client.CreateDirectChannel(toUserId) _, resp := m.Client.CreateDirectChannel(m.User.Id, toUserId)
if err != nil { if resp.Error != nil {
m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, resp.Error)
return return
} }
channelName := model.GetDMNameFromIds(toUserId, m.User.Id) channelName := model.GetDMNameFromIds(toUserId, m.User.Id)
// update our channels // update our channels
mmchannels, err := m.Client.GetChannels("") m.UpdateChannels()
if err != nil {
m.log.Debug("SendDirectMessage: Couldn't update channels")
return
}
m.Lock()
m.Team.Channels = mmchannels.Data.(*model.ChannelList)
m.Unlock()
// build & send the message // build & send the message
msg = strings.Replace(msg, "\r", "", -1) msg = strings.Replace(msg, "\r", "", -1)
@ -636,10 +641,10 @@ func (m *MMClient) GetChannels() []*model.Channel {
defer m.RUnlock() defer m.RUnlock()
var channels []*model.Channel var channels []*model.Channel
// our primary team channels first // our primary team channels first
channels = append(channels, *m.Team.Channels...) channels = append(channels, m.Team.Channels...)
for _, t := range m.OtherTeams { for _, t := range m.OtherTeams {
if t.Id != m.Team.Id { if t.Id != m.Team.Id {
channels = append(channels, *t.Channels...) channels = append(channels, t.Channels...)
} }
} }
return channels return channels
@ -651,7 +656,7 @@ func (m *MMClient) GetMoreChannels() []*model.Channel {
defer m.RUnlock() defer m.RUnlock()
var channels []*model.Channel var channels []*model.Channel
for _, t := range m.OtherTeams { for _, t := range m.OtherTeams {
channels = append(channels, *t.MoreChannels...) channels = append(channels, t.MoreChannels...)
} }
return channels return channels
} }
@ -662,9 +667,9 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string {
defer m.RUnlock() defer m.RUnlock()
var channels []*model.Channel var channels []*model.Channel
for _, t := range m.OtherTeams { for _, t := range m.OtherTeams {
channels = append(channels, *t.Channels...) channels = append(channels, t.Channels...)
if t.MoreChannels != nil { if t.MoreChannels != nil {
channels = append(channels, *t.MoreChannels...) channels = append(channels, t.MoreChannels...)
} }
for _, c := range channels { for _, c := range channels {
if c.Id == channelId { if c.Id == channelId {
@ -678,12 +683,11 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string {
func (m *MMClient) GetLastViewedAt(channelId string) int64 { func (m *MMClient) GetLastViewedAt(channelId string) int64 {
m.RLock() m.RLock()
defer m.RUnlock() defer m.RUnlock()
res, err := m.Client.GetChannel(channelId, "") res, resp := m.Client.GetChannelMember(channelId, m.User.Id, "")
if err != nil { if resp.Error != nil {
return model.GetMillis() return model.GetMillis()
} }
data := res.Data.(*model.ChannelData) return res.LastViewedAt
return data.Member.LastViewedAt
} }
func (m *MMClient) GetUsers() map[string]*model.User { func (m *MMClient) GetUsers() map[string]*model.User {
@ -701,12 +705,11 @@ func (m *MMClient) GetUser(userId string) *model.User {
defer m.Unlock() defer m.Unlock()
_, ok := m.Users[userId] _, ok := m.Users[userId]
if !ok { if !ok {
res, err := m.Client.GetProfilesByIds([]string{userId}) res, resp := m.Client.GetUser(userId, "")
if err != nil { if resp.Error != nil {
return nil return nil
} }
u := res.Data.(map[string]*model.User)[userId] m.Users[userId] = res
m.Users[userId] = u
} }
return m.Users[userId] return m.Users[userId]
} }
@ -720,36 +723,36 @@ func (m *MMClient) GetUserName(userId string) string {
} }
func (m *MMClient) GetStatus(userId string) string { func (m *MMClient) GetStatus(userId string) string {
res, err := m.Client.GetStatusesByIds([]string{userId}) res, resp := m.Client.GetUserStatus(userId, "")
if err != nil { if resp.Error != nil {
return "" return ""
} }
status := res.Data.(map[string]string) if res.Status == model.STATUS_AWAY {
if status[userId] == model.STATUS_AWAY {
return "away" return "away"
} }
if status[userId] == model.STATUS_ONLINE { if res.Status == model.STATUS_ONLINE {
return "online" return "online"
} }
return "offline" return "offline"
} }
func (m *MMClient) GetStatuses() map[string]string { func (m *MMClient) GetStatuses() map[string]string {
var ok bool var ids []string
statuses := make(map[string]string) statuses := make(map[string]string)
res, err := m.Client.GetStatuses() for id := range m.Users {
if err != nil { ids = append(ids, id)
}
res, resp := m.Client.GetUsersStatusesByIds(ids)
if resp.Error != nil {
return statuses return statuses
} }
if statuses, ok = res.Data.(map[string]string); ok { for _, status := range res {
for userId, status := range statuses { statuses[status.UserId] = "offline"
statuses[userId] = "offline" if status.Status == model.STATUS_AWAY {
if status == model.STATUS_AWAY { statuses[status.UserId] = "away"
statuses[userId] = "away" }
} if status.Status == model.STATUS_ONLINE {
if status == model.STATUS_ONLINE { statuses[status.UserId] = "online"
statuses[userId] = "online"
}
} }
} }
return statuses return statuses
@ -800,40 +803,39 @@ func (m *MMClient) StatusLoop() {
func (m *MMClient) initUser() error { func (m *MMClient) initUser() error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
initLoad, err := m.Client.GetInitialLoad()
if err != nil {
return err
}
initData := initLoad.Data.(*model.InitialLoad)
m.User = initData.User
// we only load all team data on initial login. // we only load all team data on initial login.
// all other updates are for channels from our (primary) team only. // all other updates are for channels from our (primary) team only.
//m.log.Debug("initUser(): loading all team data") //m.log.Debug("initUser(): loading all team data")
for _, v := range initData.Teams { teams, resp := m.Client.GetTeamsForUser(m.User.Id, "")
m.Client.SetTeamId(v.Id) if resp.Error != nil {
mmusers, err := m.Client.GetProfiles(0, 50000, "") return resp.Error
if err != nil { }
return errors.New(err.DetailedError) for _, team := range teams {
mmusers, resp := m.Client.GetUsersInTeam(team.Id, 0, 50000, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
} }
t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id} usermap := make(map[string]*model.User)
mmchannels, err := m.Client.GetChannels("") for _, user := range mmusers {
if err != nil { usermap[user.Id] = user
return errors.New(err.DetailedError)
} }
t.Channels = mmchannels.Data.(*model.ChannelList)
if m.mmVersion() >= 3.08 { t := &Team{Team: team, Users: usermap, Id: team.Id}
mmchannels, err = m.Client.GetMoreChannelsPage(0, 5000)
} else { mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, "")
mmchannels, err = m.Client.GetMoreChannels("") if resp.Error != nil {
return resp.Error
} }
if err != nil { t.Channels = mmchannels
return errors.New(err.DetailedError) mmchannels, resp = m.Client.GetPublicChannelsForTeam(team.Id, 0, 5000, "")
if resp.Error != nil {
return resp.Error
} }
t.MoreChannels = mmchannels.Data.(*model.ChannelList) t.MoreChannels = mmchannels
m.OtherTeams = append(m.OtherTeams, t) m.OtherTeams = append(m.OtherTeams, t)
if v.Name == m.Credentials.Team { if team.Name == m.Credentials.Team {
m.Team = t m.Team = t
m.log.Debugf("initUser(): found our team %s (id: %s)", v.Name, v.Id) m.log.Debugf("initUser(): found our team %s (id: %s)", team.Name, team.Id)
} }
// add all users // add all users
for k, v := range t.Users { for k, v := range t.Users {
@ -854,23 +856,13 @@ func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) err
return nil return nil
} }
func (m *MMClient) mmVersion() float64 {
v, _ := strconv.ParseFloat(string(m.ServerVersion[0:2])+"0"+string(m.ServerVersion[2]), 64)
if string(m.ServerVersion[4]) == "." {
v, _ = strconv.ParseFloat(m.ServerVersion[0:4], 64)
}
return v
}
func supportedVersion(version string) bool { func supportedVersion(version string) bool {
if strings.HasPrefix(version, "3.5.0") || if strings.HasPrefix(version, "3.8.0") ||
strings.HasPrefix(version, "3.6.0") ||
strings.HasPrefix(version, "3.7.0") ||
strings.HasPrefix(version, "3.8.0") ||
strings.HasPrefix(version, "3.9.0") || strings.HasPrefix(version, "3.9.0") ||
strings.HasPrefix(version, "3.10.0") || strings.HasPrefix(version, "3.10.0") ||
strings.HasPrefix(version, "4.0") || strings.HasPrefix(version, "4.0") ||
strings.HasPrefix(version, "4.1") { strings.HasPrefix(version, "4.1") ||
strings.HasPrefix(version, "4.2") {
return true return true
} }
return false return false

View File

@ -205,17 +205,43 @@ func (gitter *Gitter) GetMessage(roomID, messageID string) (*Message, error) {
} }
// SendMessage sends a message to a room // SendMessage sends a message to a room
func (gitter *Gitter) SendMessage(roomID, text string) error { func (gitter *Gitter) SendMessage(roomID, text string) (*Message, error) {
message := Message{Text: text} message := Message{Text: text}
body, _ := json.Marshal(message) body, _ := json.Marshal(message)
_, err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body) response, err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body)
if err != nil { if err != nil {
gitter.log(err) gitter.log(err)
return err return nil, err
} }
return nil err = json.Unmarshal(response, &message)
if err != nil {
gitter.log(err)
return nil, err
}
return &message, nil
}
// UpdateMessage updates a message in a room
func (gitter *Gitter) UpdateMessage(roomID, msgID, text string) (*Message, error) {
message := Message{Text: text}
body, _ := json.Marshal(message)
response, err := gitter.put(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages/"+msgID, body)
if err != nil {
gitter.log(err)
return nil, err
}
err = json.Unmarshal(response, &message)
if err != nil {
gitter.log(err)
return nil, err
}
return &message, nil
} }
// JoinRoom joins a room // JoinRoom joins a room
@ -265,7 +291,7 @@ func (gitter *Gitter) SearchRooms(room string) ([]Room, error) {
Results []Room `json:"results"` Results []Room `json:"results"`
} }
response, err := gitter.get(gitter.config.apiBaseURL + "rooms?q=" + room ) response, err := gitter.get(gitter.config.apiBaseURL + "rooms?q=" + room)
if err != nil { if err != nil {
gitter.log(err) gitter.log(err)
@ -414,6 +440,39 @@ func (gitter *Gitter) post(url string, body []byte) ([]byte, error) {
return result, nil return result, nil
} }
func (gitter *Gitter) put(url string, body []byte) ([]byte, error) {
r, err := http.NewRequest("PUT", url, bytes.NewBuffer(body))
if err != nil {
gitter.log(err)
return nil, err
}
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Accept", "application/json")
r.Header.Set("Authorization", "Bearer "+gitter.config.token)
resp, err := gitter.config.client.Do(r)
if err != nil {
gitter.log(err)
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)}
gitter.log(err)
return nil, err
}
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
gitter.log(err)
return nil, err
}
return result, nil
}
func (gitter *Gitter) delete(url string) ([]byte, error) { func (gitter *Gitter) delete(url string) ([]byte, error) {
r, err := http.NewRequest("delete", url, nil) r, err := http.NewRequest("delete", url, nil)
if err != nil { if err != nil {

View File

@ -1,32 +1,32 @@
Mattermost Licensing Mattermost Licensing
SOFTWARE LICENSING SOFTWARE LICENSING
You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
- See MIT-COMPILED-LICENSE.md included in compiled versions for details - See MIT-COMPILED-LICENSE.md included in compiled versions for details
You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways: You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:
1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or 1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com 2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/, You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0. webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of (b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license. a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license.
MATTERMOST TRADEMARK GUIDELINES MATTERMOST TRADEMARK GUIDELINES
Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark
Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
you have about using these trademarks, please email trademark@mattermost.com you have about using these trademarks, please email trademark@mattermost.com
------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces
@ -6,7 +6,7 @@ package einterfaces
import "github.com/mattermost/platform/model" import "github.com/mattermost/platform/model"
type AccountMigrationInterface interface { type AccountMigrationInterface interface {
MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string, force bool) *model.AppError
} }
var theAccountMigrationInterface AccountMigrationInterface var theAccountMigrationInterface AccountMigrationInterface

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces
@ -7,26 +7,19 @@ import (
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
) )
type ClusterMessageHandler func(msg *model.ClusterMessage)
type ClusterInterface interface { type ClusterInterface interface {
StartInterNodeCommunication() StartInterNodeCommunication()
StopInterNodeCommunication() StopInterNodeCommunication()
GetClusterInfos() []*model.ClusterInfo RegisterClusterMessageHandler(event string, crm ClusterMessageHandler)
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
ClearSessionCacheForUser(userId string)
InvalidateCacheForUser(userId string)
InvalidateCacheForChannel(channelId string)
InvalidateCacheForChannelByName(teamId, name string)
InvalidateCacheForChannelMembers(channelId string)
InvalidateCacheForChannelMembersNotifyProps(channelId string)
InvalidateCacheForChannelPosts(channelId string)
InvalidateCacheForWebhook(webhookId string)
InvalidateCacheForReactions(postId string)
Publish(event *model.WebSocketEvent)
UpdateStatus(status *model.Status)
GetLogs() ([]string, *model.AppError)
GetClusterId() string GetClusterId() string
GetClusterInfos() []*model.ClusterInfo
SendClusterMessage(cluster *model.ClusterMessage)
NotifyMsg(buf []byte)
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
GetLogs(page, perPage int) ([]string, *model.AppError)
ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError
InvalidateAllCaches() *model.AppError
} }
var theClusterInterface ClusterInterface var theClusterInterface ClusterInterface

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces

View File

@ -0,0 +1,25 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces
import "github.com/mattermost/platform/model"
type ElasticsearchInterface interface {
Start() *model.AppError
IndexPost(post *model.Post, teamId string) *model.AppError
SearchPosts(channels *model.ChannelList, searchParams []*model.SearchParams) ([]string, *model.AppError)
DeletePost(post *model.Post) *model.AppError
TestConfig(cfg *model.Config) *model.AppError
PurgeIndexes() *model.AppError
}
var theElasticsearchInterface ElasticsearchInterface
func RegisterElasticsearchInterface(newInterface ElasticsearchInterface) {
theElasticsearchInterface = newInterface
}
func GetElasticsearchInterface() ElasticsearchInterface {
return theElasticsearchInterface
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces

View File

@ -0,0 +1,23 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package jobs
import (
"github.com/mattermost/platform/model"
)
type DataRetentionInterface interface {
MakeWorker() model.Worker
MakeScheduler() model.Scheduler
}
var theDataRetentionInterface DataRetentionInterface
func RegisterDataRetentionInterface(newInterface DataRetentionInterface) {
theDataRetentionInterface = newInterface
}
func GetDataRetentionInterface() DataRetentionInterface {
return theDataRetentionInterface
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package jobs
import (
"github.com/mattermost/platform/model"
)
type ElasticsearchIndexerInterface interface {
MakeWorker() model.Worker
}
var theElasticsearchIndexerInterface ElasticsearchIndexerInterface
func RegisterElasticsearchIndexerInterface(newInterface ElasticsearchIndexerInterface) {
theElasticsearchIndexerInterface = newInterface
}
func GetElasticsearchIndexerInterface() ElasticsearchIndexerInterface {
return theElasticsearchIndexerInterface
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces
@ -33,6 +33,7 @@ type MetricsInterface interface {
IncrementMemCacheHitCounterSession() IncrementMemCacheHitCounterSession()
IncrementWebsocketEvent(eventType string) IncrementWebsocketEvent(eventType string)
IncrementWebSocketBroadcast(eventType string)
AddMemCacheHitCounter(cacheName string, amount float64) AddMemCacheHitCounter(cacheName string, amount float64)
AddMemCacheMissCounter(cacheName string, amount float64) AddMemCacheMissCounter(cacheName string, amount float64)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package einterfaces package einterfaces
@ -10,7 +10,7 @@ import (
type SamlInterface interface { type SamlInterface interface {
ConfigureSP() *model.AppError ConfigureSP() *model.AppError
BuildRequest(relayState string) (*model.SamlAuthRequest, *model.AppError) BuildRequest(relayState string) (*model.SamlAuthRequest, *model.AppError)
DoLogin(encodedXML string, relayState map[string]string, siteURL string) (*model.User, *model.AppError) DoLogin(encodedXML string, relayState map[string]string) (*model.User, *model.AppError)
GetMetadata() (string, *model.AppError) GetMetadata() (string, *model.AppError)
} }

View File

@ -1,32 +1,32 @@
Mattermost Licensing Mattermost Licensing
SOFTWARE LICENSING SOFTWARE LICENSING
You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
- See MIT-COMPILED-LICENSE.md included in compiled versions for details - See MIT-COMPILED-LICENSE.md included in compiled versions for details
You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways: You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:
1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or 1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com 2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/, You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0. webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of (b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license. a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license.
MATTERMOST TRADEMARK GUIDELINES MATTERMOST TRADEMARK GUIDELINES
Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark
Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
you have about using these trademarks, please email trademark@mattermost.com you have about using these trademarks, please email trademark@mattermost.com
------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -21,6 +21,7 @@ type AccessData struct {
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
RedirectUri string `json:"redirect_uri"` RedirectUri string `json:"redirect_uri"`
ExpiresAt int64 `json:"expires_at"` ExpiresAt int64 `json:"expires_at"`
Scope string `json:"scope"`
} }
type AccessResponse struct { type AccessResponse struct {
@ -51,7 +52,7 @@ func (ad *AccessData) IsValid() *AppError {
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "") return NewLocAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "")
} }
if len(ad.RedirectUri) > 256 { if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "") return NewLocAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "")
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -39,6 +39,7 @@ var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission
var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission
var PERMISSION_EDIT_OTHER_USERS *Permission var PERMISSION_EDIT_OTHER_USERS *Permission
var PERMISSION_READ_CHANNEL *Permission var PERMISSION_READ_CHANNEL *Permission
var PERMISSION_READ_PUBLIC_CHANNEL *Permission
var PERMISSION_PERMANENT_DELETE_USER *Permission var PERMISSION_PERMANENT_DELETE_USER *Permission
var PERMISSION_UPLOAD_FILE *Permission var PERMISSION_UPLOAD_FILE *Permission
var PERMISSION_GET_PUBLIC_LINK *Permission var PERMISSION_GET_PUBLIC_LINK *Permission
@ -47,6 +48,7 @@ var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission
var PERMISSION_MANAGE_OAUTH *Permission var PERMISSION_MANAGE_OAUTH *Permission
var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission
var PERMISSION_CREATE_POST *Permission var PERMISSION_CREATE_POST *Permission
var PERMISSION_CREATE_POST_PUBLIC *Permission
var PERMISSION_EDIT_POST *Permission var PERMISSION_EDIT_POST *Permission
var PERMISSION_EDIT_OTHERS_POSTS *Permission var PERMISSION_EDIT_OTHERS_POSTS *Permission
var PERMISSION_DELETE_POST *Permission var PERMISSION_DELETE_POST *Permission
@ -56,6 +58,11 @@ var PERMISSION_CREATE_TEAM *Permission
var PERMISSION_MANAGE_TEAM *Permission var PERMISSION_MANAGE_TEAM *Permission
var PERMISSION_IMPORT_TEAM *Permission var PERMISSION_IMPORT_TEAM *Permission
var PERMISSION_VIEW_TEAM *Permission var PERMISSION_VIEW_TEAM *Permission
var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission
var PERMISSION_MANAGE_JOBS *Permission
var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission
var PERMISSION_READ_USER_ACCESS_TOKEN *Permission
var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission
// General permission that encompases all system admin functions // General permission that encompases all system admin functions
// in the future this could be broken up to allow access to some // in the future this could be broken up to allow access to some
@ -64,9 +71,14 @@ var PERMISSION_MANAGE_SYSTEM *Permission
var ROLE_SYSTEM_USER *Role var ROLE_SYSTEM_USER *Role
var ROLE_SYSTEM_ADMIN *Role var ROLE_SYSTEM_ADMIN *Role
var ROLE_SYSTEM_POST_ALL *Role
var ROLE_SYSTEM_POST_ALL_PUBLIC *Role
var ROLE_SYSTEM_USER_ACCESS_TOKEN *Role
var ROLE_TEAM_USER *Role var ROLE_TEAM_USER *Role
var ROLE_TEAM_ADMIN *Role var ROLE_TEAM_ADMIN *Role
var ROLE_TEAM_POST_ALL *Role
var ROLE_TEAM_POST_ALL_PUBLIC *Role
var ROLE_CHANNEL_USER *Role var ROLE_CHANNEL_USER *Role
var ROLE_CHANNEL_ADMIN *Role var ROLE_CHANNEL_ADMIN *Role
@ -195,6 +207,11 @@ func InitalizePermissions() {
"authentication.permissions.read_channel.name", "authentication.permissions.read_channel.name",
"authentication.permissions.read_channel.description", "authentication.permissions.read_channel.description",
} }
PERMISSION_READ_PUBLIC_CHANNEL = &Permission{
"read_public_channel",
"authentication.permissions.read_public_channel.name",
"authentication.permissions.read_public_channel.description",
}
PERMISSION_PERMANENT_DELETE_USER = &Permission{ PERMISSION_PERMANENT_DELETE_USER = &Permission{
"permanent_delete_user", "permanent_delete_user",
"authentication.permissions.permanent_delete_user.name", "authentication.permissions.permanent_delete_user.name",
@ -235,6 +252,11 @@ func InitalizePermissions() {
"authentication.permissions.create_post.name", "authentication.permissions.create_post.name",
"authentication.permissions.create_post.description", "authentication.permissions.create_post.description",
} }
PERMISSION_CREATE_POST_PUBLIC = &Permission{
"create_post_public",
"authentication.permissions.create_post_public.name",
"authentication.permissions.create_post_public.description",
}
PERMISSION_EDIT_POST = &Permission{ PERMISSION_EDIT_POST = &Permission{
"edit_post", "edit_post",
"authentication.permissions.edit_post.name", "authentication.permissions.edit_post.name",
@ -280,6 +302,31 @@ func InitalizePermissions() {
"authentication.permissions.view_team.name", "authentication.permissions.view_team.name",
"authentication.permissions.view_team.description", "authentication.permissions.view_team.description",
} }
PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{
"list_users_without_team",
"authentication.permissions.list_users_without_team.name",
"authentication.permissions.list_users_without_team.description",
}
PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{
"create_user_access_token",
"authentication.permissions.create_user_access_token.name",
"authentication.permissions.create_user_access_token.description",
}
PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{
"read_user_access_token",
"authentication.permissions.read_user_access_token.name",
"authentication.permissions.read_user_access_token.description",
}
PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{
"revoke_user_access_token",
"authentication.permissions.revoke_user_access_token.name",
"authentication.permissions.revoke_user_access_token.description",
}
PERMISSION_MANAGE_JOBS = &Permission{
"manage_jobs",
"authentication.permisssions.manage_jobs.name",
"authentication.permisssions.manage_jobs.description",
}
} }
func InitalizeRoles() { func InitalizeRoles() {
@ -293,7 +340,6 @@ func InitalizeRoles() {
[]string{ []string{
PERMISSION_READ_CHANNEL.Id, PERMISSION_READ_CHANNEL.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id,
PERMISSION_UPLOAD_FILE.Id, PERMISSION_UPLOAD_FILE.Id,
PERMISSION_GET_PUBLIC_LINK.Id, PERMISSION_GET_PUBLIC_LINK.Id,
PERMISSION_CREATE_POST.Id, PERMISSION_CREATE_POST.Id,
@ -326,17 +372,38 @@ func InitalizeRoles() {
[]string{ []string{
PERMISSION_LIST_TEAM_CHANNELS.Id, PERMISSION_LIST_TEAM_CHANNELS.Id,
PERMISSION_JOIN_PUBLIC_CHANNELS.Id, PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
PERMISSION_READ_PUBLIC_CHANNEL.Id,
PERMISSION_VIEW_TEAM.Id, PERMISSION_VIEW_TEAM.Id,
}, },
} }
BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER
ROLE_TEAM_POST_ALL = &Role{
"team_post_all",
"authentication.roles.team_post_all.name",
"authentication.roles.team_post_all.description",
[]string{
PERMISSION_CREATE_POST.Id,
},
}
BuiltInRoles[ROLE_TEAM_POST_ALL.Id] = ROLE_TEAM_POST_ALL
ROLE_TEAM_POST_ALL_PUBLIC = &Role{
"team_post_all_public",
"authentication.roles.team_post_all_public.name",
"authentication.roles.team_post_all_public.description",
[]string{
PERMISSION_CREATE_POST_PUBLIC.Id,
},
}
BuiltInRoles[ROLE_TEAM_POST_ALL_PUBLIC.Id] = ROLE_TEAM_POST_ALL_PUBLIC
ROLE_TEAM_ADMIN = &Role{ ROLE_TEAM_ADMIN = &Role{
"team_admin", "team_admin",
"authentication.roles.team_admin.name", "authentication.roles.team_admin.name",
"authentication.roles.team_admin.description", "authentication.roles.team_admin.description",
[]string{ []string{
PERMISSION_EDIT_OTHERS_POSTS.Id, PERMISSION_EDIT_OTHERS_POSTS.Id,
PERMISSION_ADD_USER_TO_TEAM.Id,
PERMISSION_REMOVE_USER_FROM_TEAM.Id, PERMISSION_REMOVE_USER_FROM_TEAM.Id,
PERMISSION_MANAGE_TEAM.Id, PERMISSION_MANAGE_TEAM.Id,
PERMISSION_IMPORT_TEAM.Id, PERMISSION_IMPORT_TEAM.Id,
@ -358,10 +425,42 @@ func InitalizeRoles() {
PERMISSION_CREATE_DIRECT_CHANNEL.Id, PERMISSION_CREATE_DIRECT_CHANNEL.Id,
PERMISSION_CREATE_GROUP_CHANNEL.Id, PERMISSION_CREATE_GROUP_CHANNEL.Id,
PERMISSION_PERMANENT_DELETE_USER.Id, PERMISSION_PERMANENT_DELETE_USER.Id,
PERMISSION_MANAGE_OAUTH.Id,
}, },
} }
BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER
ROLE_SYSTEM_POST_ALL = &Role{
"system_post_all",
"authentication.roles.system_post_all.name",
"authentication.roles.system_post_all.description",
[]string{
PERMISSION_CREATE_POST.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_POST_ALL.Id] = ROLE_SYSTEM_POST_ALL
ROLE_SYSTEM_POST_ALL_PUBLIC = &Role{
"system_post_all_public",
"authentication.roles.system_post_all_public.name",
"authentication.roles.system_post_all_public.description",
[]string{
PERMISSION_CREATE_POST_PUBLIC.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_POST_ALL_PUBLIC.Id] = ROLE_SYSTEM_POST_ALL_PUBLIC
ROLE_SYSTEM_USER_ACCESS_TOKEN = &Role{
"system_user_access_token",
"authentication.roles.system_user_access_token.name",
"authentication.roles.system_user_access_token.description",
[]string{
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_USER_ACCESS_TOKEN.Id] = ROLE_SYSTEM_USER_ACCESS_TOKEN
ROLE_SYSTEM_ADMIN = &Role{ ROLE_SYSTEM_ADMIN = &Role{
"system_admin", "system_admin",
"authentication.roles.global_admin.name", "authentication.roles.global_admin.name",
@ -378,6 +477,8 @@ func InitalizeRoles() {
PERMISSION_MANAGE_SYSTEM.Id, PERMISSION_MANAGE_SYSTEM.Id,
PERMISSION_MANAGE_ROLES.Id, PERMISSION_MANAGE_ROLES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id,
PERMISSION_DELETE_PUBLIC_CHANNEL.Id, PERMISSION_DELETE_PUBLIC_CHANNEL.Id,
PERMISSION_CREATE_PUBLIC_CHANNEL.Id, PERMISSION_CREATE_PUBLIC_CHANNEL.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id,
@ -391,6 +492,13 @@ func InitalizeRoles() {
PERMISSION_DELETE_POST.Id, PERMISSION_DELETE_POST.Id,
PERMISSION_DELETE_OTHERS_POSTS.Id, PERMISSION_DELETE_OTHERS_POSTS.Id,
PERMISSION_CREATE_TEAM.Id, PERMISSION_CREATE_TEAM.Id,
PERMISSION_ADD_USER_TO_TEAM.Id,
PERMISSION_LIST_USERS_WITHOUT_TEAM.Id,
PERMISSION_MANAGE_JOBS.Id,
PERMISSION_CREATE_POST_PUBLIC.Id,
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
}, },
ROLE_TEAM_USER.Permissions..., ROLE_TEAM_USER.Permissions...,
), ),

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -6,6 +6,7 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"net/http"
) )
const ( const (
@ -25,6 +26,14 @@ type AuthData struct {
Scope string `json:"scope"` Scope string `json:"scope"`
} }
type AuthorizeRequest struct {
ResponseType string `json:"response_type"`
ClientId string `json:"client_id"`
RedirectUri string `json:"redirect_uri"`
Scope string `json:"scope"`
State string `json:"state"`
}
// IsValid validates the AuthData and returns an error if it isn't configured // IsValid validates the AuthData and returns an error if it isn't configured
// correctly. // correctly.
func (ad *AuthData) IsValid() *AppError { func (ad *AuthData) IsValid() *AppError {
@ -49,7 +58,7 @@ func (ad *AuthData) IsValid() *AppError {
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId) return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId)
} }
if len(ad.RedirectUri) > 256 { if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId) return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId)
} }
@ -64,6 +73,33 @@ func (ad *AuthData) IsValid() *AppError {
return nil return nil
} }
// IsValid validates the AuthorizeRequest and returns an error if it isn't configured
// correctly.
func (ar *AuthorizeRequest) IsValid() *AppError {
if len(ar.ClientId) != 26 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.ResponseType) == 0 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.State) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.Scope) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
return nil
}
func (ad *AuthData) PreSave() { func (ad *AuthData) PreSave() {
if ad.ExpiresIn == 0 { if ad.ExpiresIn == 0 {
ad.ExpiresIn = AUTHCODE_EXPIRE_TIME ad.ExpiresIn = AUTHCODE_EXPIRE_TIME
@ -98,6 +134,26 @@ func AuthDataFromJson(data io.Reader) *AuthData {
} }
} }
func (ar *AuthorizeRequest) ToJson() string {
b, err := json.Marshal(ar)
if err != nil {
return ""
} else {
return string(b)
}
}
func AuthorizeRequestFromJson(data io.Reader) *AuthorizeRequest {
decoder := json.NewDecoder(data)
var ar AuthorizeRequest
err := decoder.Decode(&ar)
if err == nil {
return &ar
} else {
return nil
}
}
func (ad *AuthData) IsExpired() bool { func (ad *AuthData) IsExpired() bool {
if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) { if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -46,6 +46,13 @@ type Channel struct {
CreatorId string `json:"creator_id"` CreatorId string `json:"creator_id"`
} }
type ChannelPatch struct {
DisplayName *string `json:"display_name"`
Name *string `json:"name"`
Header *string `json:"header"`
Purpose *string `json:"purpose"`
}
func (o *Channel) ToJson() string { func (o *Channel) ToJson() string {
b, err := json.Marshal(o) b, err := json.Marshal(o)
if err != nil { if err != nil {
@ -55,6 +62,15 @@ func (o *Channel) ToJson() string {
} }
} }
func (o *ChannelPatch) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelFromJson(data io.Reader) *Channel { func ChannelFromJson(data io.Reader) *Channel {
decoder := json.NewDecoder(data) decoder := json.NewDecoder(data)
var o Channel var o Channel
@ -66,6 +82,17 @@ func ChannelFromJson(data io.Reader) *Channel {
} }
} }
func ChannelPatchFromJson(data io.Reader) *ChannelPatch {
decoder := json.NewDecoder(data)
var o ChannelPatch
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *Channel) Etag() string { func (o *Channel) Etag() string {
return Etag(o.Id, o.UpdateAt) return Etag(o.Id, o.UpdateAt)
} }
@ -137,6 +164,24 @@ func (o *Channel) IsGroupOrDirect() bool {
return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP
} }
func (o *Channel) Patch(patch *ChannelPatch) {
if patch.DisplayName != nil {
o.DisplayName = *patch.DisplayName
}
if patch.Name != nil {
o.Name = *patch.Name
}
if patch.Header != nil {
o.Header = *patch.Header
}
if patch.Purpose != nil {
o.Purpose = *patch.Purpose
}
}
func GetDMNameFromIds(userId1, userId2 string) string { func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 { if userId1 > userId2 {
return userId2 + "__" + userId1 return userId2 + "__" + userId1

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -50,3 +50,14 @@ func ChannelListFromJson(data io.Reader) *ChannelList {
return nil return nil
} }
} }
func ChannelSliceFromJson(data io.Reader) []*Channel {
decoder := json.NewDecoder(data)
var o []*Channel
err := decoder.Decode(&o)
if err == nil {
return o
} else {
return nil
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -19,11 +19,11 @@ const (
) )
type ChannelUnread struct { type ChannelUnread struct {
TeamId string TeamId string `json:"team_id"`
TotalMsgCount int64 ChannelId string `json:"channel_id"`
MsgCount int64 MsgCount int64 `json:"msg_count"`
MentionCount int64 MentionCount int64 `json:"mention_count"`
NotifyProps StringMap NotifyProps StringMap `json:"-"`
} }
type ChannelMember struct { type ChannelMember struct {
@ -47,6 +47,15 @@ func (o *ChannelMembers) ToJson() string {
} }
} }
func (o *ChannelUnread) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelMembersFromJson(data io.Reader) *ChannelMembers { func ChannelMembersFromJson(data io.Reader) *ChannelMembers {
decoder := json.NewDecoder(data) decoder := json.NewDecoder(data)
var o ChannelMembers var o ChannelMembers
@ -58,6 +67,17 @@ func ChannelMembersFromJson(data io.Reader) *ChannelMembers {
} }
} }
func ChannelUnreadFromJson(data io.Reader) *ChannelUnread {
decoder := json.NewDecoder(data)
var o ChannelUnread
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *ChannelMember) ToJson() string { func (o *ChannelMember) ToJson() string {
b, err := json.Marshal(o) b, err := json.Marshal(o)
if err != nil { if err != nil {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -1191,17 +1191,6 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
} }
} }
// SCHEDULED FOR DEPRECATION IN 3.7 - use GetMoreChannelsPage instead
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
}
}
// GetMoreChannelsPage will return a page of open channels the user is not in based on // GetMoreChannelsPage will return a page of open channels the user is not in based on
// the provided offset and limit. Must be authenticated. // the provided offset and limit. Must be authenticated.
func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) { func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) {
@ -1333,22 +1322,6 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
} }
} }
// UpdateLastViewedAt will mark a channel as read.
// The channelId indicates the channel to mark as read. If active is true, push notifications
// will be cleared if there are unread messages. The default for active is true.
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *AppError) {
data := make(map[string]interface{})
data["active"] = active
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", StringInterfaceToJson(data)); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
// ViewChannel performs all the actions related to viewing a channel. This includes marking // ViewChannel performs all the actions related to viewing a channel. This includes marking
// the channel and the previous one as read, and marking the channel as being actively viewed. // the channel and the previous one as read, and marking the channel as being actively viewed.
// ChannelId is required but may be blank to indicate no channel is being viewed. // ChannelId is required but may be blank to indicate no channel is being viewed.
@ -1533,6 +1506,16 @@ func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) {
} }
} }
func (c *Client) GetPinnedPosts(channelId string) (*Result, *AppError) {
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", "", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) {
return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType)
} }
@ -1782,22 +1765,6 @@ func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) {
} }
} }
// SetActiveChannel sets the the channel id the user is currently viewing.
// The channelId key is required but the value can be blank. Returns standard
// response.
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
func (c *Client) SetActiveChannel(channelId string) (*Result, *AppError) {
data := map[string]string{}
data["channel_id"] = channelId
if r, err := c.DoApiPost("/users/status/set_active_channel", MapToJson(data)); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil { if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil {
return nil, err return nil, err
@ -1874,15 +1841,14 @@ func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) {
} }
} }
// GetTeamStats will return a team stats object containing the number of users on the team // GetTeamByName will return a team object based on the team name provided. Must be authenticated.
// based on the team id provided. Must be authenticated.
func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) { func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) {
if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil { if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil {
return nil, err return nil, err
} else { } else {
defer closeBody(r) defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil
} }
} }
@ -2389,3 +2355,23 @@ func (c *Client) UpdateChannelRoles(channelId string, userId string, roles strin
} }
} }
} }
func (c *Client) PinPost(channelId string, postId string) (*Result, *AppError) {
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/pin", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}
func (c *Client) UnpinPost(channelId string, postId string) (*Result, *AppError) {
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/unpin", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
"os"
)
const (
CDS_OFFLINE_AFTER_MILLIS = 1000 * 60 * 30 // 30 minutes
CDS_TYPE_APP = "mattermost_app"
)
type ClusterDiscovery struct {
Id string `json:"id"`
Type string `json:"type"`
ClusterName string `json:"cluster_name"`
Hostname string `json:"hostname"`
GossipPort int32 `json:"gossip_port"`
Port int32 `json:"port"`
CreateAt int64 `json:"create_at"`
LastPingAt int64 `json:"last_ping_at"`
}
func (o *ClusterDiscovery) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
o.LastPingAt = o.CreateAt
}
}
func (o *ClusterDiscovery) AutoFillHostname() {
// attempt to set the hostname from the OS
if len(o.Hostname) == 0 {
if hn, err := os.Hostname(); err == nil {
o.Hostname = hn
}
}
}
func (o *ClusterDiscovery) AutoFillIpAddress() {
// attempt to set the hostname to the first non-local IP address
if len(o.Hostname) == 0 {
o.Hostname = GetServerIpAddress()
}
}
func (o *ClusterDiscovery) IsEqual(in *ClusterDiscovery) bool {
if in == nil {
return false
}
if o.Type != in.Type {
return false
}
if o.ClusterName != in.ClusterName {
return false
}
if o.Hostname != in.Hostname {
return false
}
return true
}
func FilterClusterDiscovery(vs []*ClusterDiscovery, f func(*ClusterDiscovery) bool) []*ClusterDiscovery {
copy := make([]*ClusterDiscovery, 0)
for _, v := range vs {
if f(v) {
copy = append(copy, v)
}
}
return copy
}
func (o *ClusterDiscovery) IsValid() *AppError {
if len(o.Id) != 26 {
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "")
}
if len(o.ClusterName) == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "ClusterName must be set", nil, "")
}
if len(o.Type) == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "Type must be set", nil, "")
}
if len(o.Hostname) == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "Hostname must be set", nil, "")
}
if o.CreateAt == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "CreateAt must be set", nil, "")
}
if o.LastPingAt == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "LastPingAt must be set", nil, "")
}
return nil
}
func (o *ClusterDiscovery) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func ClusterDiscoveryFromJson(data io.Reader) *ClusterDiscovery {
decoder := json.NewDecoder(data)
var me ClusterDiscovery
err := decoder.Decode(&me)
if err == nil {
return &me
}
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -6,16 +6,14 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"strings"
) )
type ClusterInfo struct { type ClusterInfo struct {
Id string `json:"id"` Version string `json:"version"`
Version string `json:"version"` ConfigHash string `json:"config_hash"`
ConfigHash string `json:"config_hash"` IpAddress string `json:"ipaddress"`
InterNodeUrl string `json:"internode_url"` Hostname string `json:"hostname"`
Hostname string `json:"hostname"`
LastSuccessfulPing int64 `json:"last_ping"`
IsAlive bool `json:"is_alive"`
} }
func (me *ClusterInfo) ToJson() string { func (me *ClusterInfo) ToJson() string {
@ -27,6 +25,11 @@ func (me *ClusterInfo) ToJson() string {
} }
} }
func (me *ClusterInfo) Copy() *ClusterInfo {
json := me.ToJson()
return ClusterInfoFromJson(strings.NewReader(json))
}
func ClusterInfoFromJson(data io.Reader) *ClusterInfo { func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
decoder := json.NewDecoder(data) decoder := json.NewDecoder(data)
var me ClusterInfo var me ClusterInfo
@ -38,14 +41,6 @@ func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
} }
} }
func (me *ClusterInfo) HaveEstablishedInitialContact() bool {
if me.Id != "" {
return true
}
return false
}
func ClusterInfosToJson(objmap []*ClusterInfo) string { func ClusterInfosToJson(objmap []*ClusterInfo) string {
if b, err := json.Marshal(objmap); err != nil { if b, err := json.Marshal(objmap); err != nil {
return "" return ""

View File

@ -0,0 +1,55 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
CLUSTER_EVENT_PUBLISH = "publish"
CLUSTER_EVENT_UPDATE_STATUS = "update_status"
CLUSTER_EVENT_INVALIDATE_ALL_CACHES = "inv_all_caches"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS = "inv_reactions"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOK = "inv_webhook"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS = "inv_channel_posts"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS = "inv_channel_members_notify_props"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS = "inv_channel_members"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME = "inv_channel_name"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER = "inv_user"
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
CLUSTER_SEND_BEST_EFFORT = "best_effort"
CLUSTER_SEND_RELIABLE = "reliable"
)
type ClusterMessage struct {
Event string `json:"event"`
SendType string `json:"-"`
WaitForAllToSend bool `json:"-"`
Data string `json:"data,omitempty"`
Props map[string]string `json:"props,omitempty"`
}
func (o *ClusterMessage) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ClusterMessageFromJson(data io.Reader) *ClusterMessage {
decoder := json.NewDecoder(data)
var o ClusterMessage
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -6,13 +6,20 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
goi18n "github.com/nicksnyder/go-i18n/i18n"
) )
type CommandArgs struct { type CommandArgs struct {
ChannelId string `json:"channel_id"` UserId string `json:"user_id"`
RootId string `json:"root_id"` ChannelId string `json:"channel_id"`
ParentId string `json:"parent_id"` TeamId string `json:"team_id"`
Command string `json:"command"` RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
Command string `json:"command"`
SiteURL string `json:"-"`
T goi18n.TranslateFunc `json:"-"`
Session Session `json:"-"`
} }
func (o *CommandArgs) ToJson() string { func (o *CommandArgs) ToJson() string {

View File

@ -1,11 +1,10 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
) )
@ -40,14 +39,8 @@ func CommandResponseFromJson(data io.Reader) *CommandResponse {
return nil return nil
} }
// Ensure attachment fields are stored as strings o.Text = ExpandAnnouncement(o.Text)
for _, attachment := range o.Attachments { o.Attachments = ProcessSlackAttachments(o.Attachments)
for _, field := range attachment.Fields {
if field.Value != nil {
field.Value = fmt.Sprintf("%v", field.Value)
}
}
}
return &o return &o
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,9 +1,10 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
import ( import (
"regexp"
"time" "time"
) )
@ -64,6 +65,15 @@ func CompliancePostHeader() []string {
} }
} }
func cleanComplianceStrings(in string) string {
if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched {
return "'" + in
} else {
return in
}
}
func (me *CompliancePost) Row() []string { func (me *CompliancePost) Row() []string {
postDeleteAt := "" postDeleteAt := ""
@ -77,15 +87,15 @@ func (me *CompliancePost) Row() []string {
} }
return []string{ return []string{
me.TeamName, cleanComplianceStrings(me.TeamName),
me.TeamDisplayName, cleanComplianceStrings(me.TeamDisplayName),
me.ChannelName, cleanComplianceStrings(me.ChannelName),
me.ChannelDisplayName, cleanComplianceStrings(me.ChannelDisplayName),
me.UserUsername, cleanComplianceStrings(me.UserUsername),
me.UserEmail, cleanComplianceStrings(me.UserEmail),
me.UserNickname, cleanComplianceStrings(me.UserNickname),
me.PostId, me.PostId,
time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339), time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339),
@ -95,7 +105,7 @@ func (me *CompliancePost) Row() []string {
me.PostRootId, me.PostRootId,
me.PostParentId, me.PostParentId,
me.PostOriginalId, me.PostOriginalId,
me.PostMessage, cleanComplianceStrings(me.PostMessage),
me.PostType, me.PostType,
me.PostProps, me.PostProps,
me.PostHashtags, me.PostHashtags,

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -6,6 +6,7 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"net/http"
"net/url" "net/url"
) )
@ -32,12 +33,17 @@ const (
WEBSERVER_MODE_GZIP = "gzip" WEBSERVER_MODE_GZIP = "gzip"
WEBSERVER_MODE_DISABLED = "disabled" WEBSERVER_MODE_DISABLED = "disabled"
GENERIC_NOTIFICATION = "generic" GENERIC_NO_CHANNEL_NOTIFICATION = "generic_no_channel"
FULL_NOTIFICATION = "full" GENERIC_NOTIFICATION = "generic"
FULL_NOTIFICATION = "full"
DIRECT_MESSAGE_ANY = "any" DIRECT_MESSAGE_ANY = "any"
DIRECT_MESSAGE_TEAM = "team" DIRECT_MESSAGE_TEAM = "team"
SHOW_USERNAME = "username"
SHOW_NICKNAME_FULLNAME = "nickname_full_name"
SHOW_FULLNAME = "full_name"
PERMISSIONS_ALL = "all" PERMISSIONS_ALL = "all"
PERMISSIONS_CHANNEL_ADMIN = "channel_admin" PERMISSIONS_CHANNEL_ADMIN = "channel_admin"
PERMISSIONS_TEAM_ADMIN = "team_admin" PERMISSIONS_TEAM_ADMIN = "team_admin"
@ -60,6 +66,9 @@ const (
EMAIL_BATCHING_BUFFER_SIZE = 256 EMAIL_BATCHING_BUFFER_SIZE = 256
EMAIL_BATCHING_INTERVAL = 30 EMAIL_BATCHING_INTERVAL = 30
EMAIL_NOTIFICATION_CONTENTS_FULL = "full"
EMAIL_NOTIFICATION_CONTENTS_GENERIC = "generic"
SITENAME_MAX_LENGTH = 30 SITENAME_MAX_LENGTH = 30
SERVICE_SETTINGS_DEFAULT_SITE_URL = "" SERVICE_SETTINGS_DEFAULT_SITE_URL = ""
@ -75,12 +84,15 @@ const (
EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION = "" EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION = ""
SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/" SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/"
SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/" SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/"
SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/" SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/"
SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/" SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/"
SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/" SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/"
SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com" SUPPORT_SETTINGS_DEFAULT_ADMINISTRATORS_GUIDE_LINK = "https://about.mattermost.com/administrators-guide/"
SUPPORT_SETTINGS_DEFAULT_TROUBLESHOOTING_FORUM_LINK = "https://about.mattermost.com/troubleshooting-forum/"
SUPPORT_SETTINGS_DEFAULT_COMMERCIAL_SUPPORT_LINK = "https://about.mattermost.com/commercial-support/"
SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com"
LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = "" LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = "" LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
@ -107,10 +119,20 @@ const (
WEBRTC_SETTINGS_DEFAULT_TURN_URI = "" WEBRTC_SETTINGS_DEFAULT_TURN_URI = ""
ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS = 2500 ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS = 2500
ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR = "#f2a93b"
ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR = "#333333"
ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL = ""
ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME = ""
ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD = ""
ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS = 1
ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS = 1
) )
type ServiceSettings struct { type ServiceSettings struct {
SiteURL *string SiteURL *string
LicenseFileLocation *string
ListenAddress string ListenAddress string
ConnectionSecurity *string ConnectionSecurity *string
TLSCertFile *string TLSCertFile *string
@ -121,6 +143,7 @@ type ServiceSettings struct {
ReadTimeout *int ReadTimeout *int
WriteTimeout *int WriteTimeout *int
MaximumLoginAttempts int MaximumLoginAttempts int
GoroutineHealthThreshold *int
GoogleDeveloperKey string GoogleDeveloperKey string
EnableOAuthServiceProvider bool EnableOAuthServiceProvider bool
EnableIncomingWebhooks bool EnableIncomingWebhooks bool
@ -129,6 +152,7 @@ type ServiceSettings struct {
EnableOnlyAdminIntegrations *bool EnableOnlyAdminIntegrations *bool
EnablePostUsernameOverride bool EnablePostUsernameOverride bool
EnablePostIconOverride bool EnablePostIconOverride bool
EnableAPIv3 *bool
EnableLinkPreviews *bool EnableLinkPreviews *bool
EnableTesting bool EnableTesting bool
EnableDeveloper *bool EnableDeveloper *bool
@ -136,6 +160,7 @@ type ServiceSettings struct {
EnableInsecureOutgoingConnections *bool EnableInsecureOutgoingConnections *bool
EnableMultifactorAuthentication *bool EnableMultifactorAuthentication *bool
EnforceMultifactorAuthentication *bool EnforceMultifactorAuthentication *bool
EnableUserAccessTokens *bool
AllowCorsFrom *string AllowCorsFrom *string
SessionLengthWebInDays *int SessionLengthWebInDays *int
SessionLengthMobileInDays *int SessionLengthMobileInDays *int
@ -145,19 +170,28 @@ type ServiceSettings struct {
WebsocketPort *int WebsocketPort *int
WebserverMode *string WebserverMode *string
EnableCustomEmoji *bool EnableCustomEmoji *bool
EnableEmojiPicker *bool
RestrictCustomEmojiCreation *string RestrictCustomEmojiCreation *string
RestrictPostDelete *string RestrictPostDelete *string
AllowEditPost *string AllowEditPost *string
PostEditTimeLimit *int PostEditTimeLimit *int
TimeBetweenUserTypingUpdatesMilliseconds *int64 TimeBetweenUserTypingUpdatesMilliseconds *int64
EnablePostSearch *bool
EnableUserTypingMessages *bool EnableUserTypingMessages *bool
EnableChannelViewedMessages *bool
EnableUserStatuses *bool
ClusterLogTimeoutMilliseconds *int ClusterLogTimeoutMilliseconds *int
} }
type ClusterSettings struct { type ClusterSettings struct {
Enable *bool Enable *bool
InterNodeListenAddress *string ClusterName *string
InterNodeUrls []string OverrideHostname *string
UseIpAddress *bool
UseExperimentalGossip *bool
ReadOnlyConfig *bool
GossipPort *int
StreamingPort *int
} }
type MetricsSettings struct { type MetricsSettings struct {
@ -181,13 +215,15 @@ type SSOSettings struct {
} }
type SqlSettings struct { type SqlSettings struct {
DriverName string DriverName string
DataSource string DataSource string
DataSourceReplicas []string DataSourceReplicas []string
MaxIdleConns int DataSourceSearchReplicas []string
MaxOpenConns int MaxIdleConns int
Trace bool MaxOpenConns int
AtRestEncryptKey string Trace bool
AtRestEncryptKey string
QueryTimeout *int
} }
type LogSettings struct { type LogSettings struct {
@ -210,17 +246,14 @@ type PasswordSettings struct {
} }
type FileSettings struct { type FileSettings struct {
EnableFileAttachments *bool
EnableMobileUpload *bool
EnableMobileDownload *bool
MaxFileSize *int64 MaxFileSize *int64
DriverName string DriverName string
Directory string Directory string
EnablePublicLink bool EnablePublicLink bool
PublicLinkSalt *string PublicLinkSalt *string
ThumbnailWidth int
ThumbnailHeight int
PreviewWidth int
PreviewHeight int
ProfileWidth int
ProfileHeight int
InitialFont string InitialFont string
AmazonS3AccessKeyId string AmazonS3AccessKeyId string
AmazonS3SecretAccessKey string AmazonS3SecretAccessKey string
@ -228,30 +261,34 @@ type FileSettings struct {
AmazonS3Region string AmazonS3Region string
AmazonS3Endpoint string AmazonS3Endpoint string
AmazonS3SSL *bool AmazonS3SSL *bool
AmazonS3SignV2 *bool
AmazonS3SSE *bool
} }
type EmailSettings struct { type EmailSettings struct {
EnableSignUpWithEmail bool EnableSignUpWithEmail bool
EnableSignInWithEmail *bool EnableSignInWithEmail *bool
EnableSignInWithUsername *bool EnableSignInWithUsername *bool
SendEmailNotifications bool SendEmailNotifications bool
RequireEmailVerification bool RequireEmailVerification bool
FeedbackName string FeedbackName string
FeedbackEmail string FeedbackEmail string
FeedbackOrganization *string FeedbackOrganization *string
SMTPUsername string EnableSMTPAuth *bool
SMTPPassword string SMTPUsername string
SMTPServer string SMTPPassword string
SMTPPort string SMTPServer string
ConnectionSecurity string SMTPPort string
InviteSalt string ConnectionSecurity string
PasswordResetSalt string InviteSalt string
SendPushNotifications *bool SendPushNotifications *bool
PushNotificationServer *string PushNotificationServer *string
PushNotificationContents *string PushNotificationContents *string
EnableEmailBatching *bool EnableEmailBatching *bool
EmailBatchingBufferSize *int EmailBatchingBufferSize *int
EmailBatchingInterval *int EmailBatchingInterval *int
SkipServerCertificateVerification *bool
EmailNotificationContentsType *string
} }
type RateLimitSettings struct { type RateLimitSettings struct {
@ -269,35 +306,48 @@ type PrivacySettings struct {
} }
type SupportSettings struct { type SupportSettings struct {
TermsOfServiceLink *string TermsOfServiceLink *string
PrivacyPolicyLink *string PrivacyPolicyLink *string
AboutLink *string AboutLink *string
HelpLink *string HelpLink *string
ReportAProblemLink *string ReportAProblemLink *string
SupportEmail *string AdministratorsGuideLink *string
TroubleshootingForumLink *string
CommercialSupportLink *string
SupportEmail *string
}
type AnnouncementSettings struct {
EnableBanner *bool
BannerText *string
BannerColor *string
BannerTextColor *string
AllowBannerDismissal *bool
} }
type TeamSettings struct { type TeamSettings struct {
SiteName string SiteName string
MaxUsersPerTeam int MaxUsersPerTeam int
EnableTeamCreation bool EnableTeamCreation bool
EnableUserCreation bool EnableUserCreation bool
EnableOpenServer *bool EnableOpenServer *bool
RestrictCreationToDomains string RestrictCreationToDomains string
EnableCustomBrand *bool EnableCustomBrand *bool
CustomBrandText *string CustomBrandText *string
CustomDescriptionText *string CustomDescriptionText *string
RestrictDirectMessage *string RestrictDirectMessage *string
RestrictTeamInvite *string RestrictTeamInvite *string
RestrictPublicChannelManagement *string RestrictPublicChannelManagement *string
RestrictPrivateChannelManagement *string RestrictPrivateChannelManagement *string
RestrictPublicChannelCreation *string RestrictPublicChannelCreation *string
RestrictPrivateChannelCreation *string RestrictPrivateChannelCreation *string
RestrictPublicChannelDeletion *string RestrictPublicChannelDeletion *string
RestrictPrivateChannelDeletion *string RestrictPrivateChannelDeletion *string
UserStatusAwayTimeout *int64 RestrictPrivateChannelManageMembers *string
MaxChannelsPerTeam *int64 UserStatusAwayTimeout *int64
MaxNotificationsPerChannel *int64 MaxChannelsPerTeam *int64
MaxNotificationsPerChannel *int64
TeammateNameDisplay *string
} }
type LdapSettings struct { type LdapSettings struct {
@ -389,29 +439,58 @@ type WebrtcSettings struct {
TurnSharedKey *string TurnSharedKey *string
} }
type ElasticsearchSettings struct {
ConnectionUrl *string
Username *string
Password *string
EnableIndexing *bool
EnableSearching *bool
Sniff *bool
PostIndexReplicas *int
PostIndexShards *int
}
type DataRetentionSettings struct {
Enable *bool
}
type JobSettings struct {
RunJobs *bool
RunScheduler *bool
}
type PluginSettings struct {
Plugins map[string]interface{}
}
type Config struct { type Config struct {
ServiceSettings ServiceSettings ServiceSettings ServiceSettings
TeamSettings TeamSettings TeamSettings TeamSettings
SqlSettings SqlSettings SqlSettings SqlSettings
LogSettings LogSettings LogSettings LogSettings
PasswordSettings PasswordSettings PasswordSettings PasswordSettings
FileSettings FileSettings FileSettings FileSettings
EmailSettings EmailSettings EmailSettings EmailSettings
RateLimitSettings RateLimitSettings RateLimitSettings RateLimitSettings
PrivacySettings PrivacySettings PrivacySettings PrivacySettings
SupportSettings SupportSettings SupportSettings SupportSettings
GitLabSettings SSOSettings AnnouncementSettings AnnouncementSettings
GoogleSettings SSOSettings GitLabSettings SSOSettings
Office365Settings SSOSettings GoogleSettings SSOSettings
LdapSettings LdapSettings Office365Settings SSOSettings
ComplianceSettings ComplianceSettings LdapSettings LdapSettings
LocalizationSettings LocalizationSettings ComplianceSettings ComplianceSettings
SamlSettings SamlSettings LocalizationSettings LocalizationSettings
NativeAppSettings NativeAppSettings SamlSettings SamlSettings
ClusterSettings ClusterSettings NativeAppSettings NativeAppSettings
MetricsSettings MetricsSettings ClusterSettings ClusterSettings
AnalyticsSettings AnalyticsSettings MetricsSettings MetricsSettings
WebrtcSettings WebrtcSettings AnalyticsSettings AnalyticsSettings
WebrtcSettings WebrtcSettings
ElasticsearchSettings ElasticsearchSettings
DataRetentionSettings DataRetentionSettings
JobSettings JobSettings
PluginSettings PluginSettings
} }
func (o *Config) ToJson() string { func (o *Config) ToJson() string {
@ -453,27 +532,52 @@ func (o *Config) SetDefaults() {
o.SqlSettings.AtRestEncryptKey = NewRandomString(32) o.SqlSettings.AtRestEncryptKey = NewRandomString(32)
} }
if o.SqlSettings.QueryTimeout == nil {
o.SqlSettings.QueryTimeout = new(int)
*o.SqlSettings.QueryTimeout = 30
}
if o.FileSettings.AmazonS3Endpoint == "" { if o.FileSettings.AmazonS3Endpoint == "" {
// Defaults to "s3.amazonaws.com" // Defaults to "s3.amazonaws.com"
o.FileSettings.AmazonS3Endpoint = "s3.amazonaws.com" o.FileSettings.AmazonS3Endpoint = "s3.amazonaws.com"
} }
if o.FileSettings.AmazonS3Region == "" {
// Defaults to "us-east-1" region.
o.FileSettings.AmazonS3Region = "us-east-1"
}
if o.FileSettings.AmazonS3SSL == nil { if o.FileSettings.AmazonS3SSL == nil {
o.FileSettings.AmazonS3SSL = new(bool) o.FileSettings.AmazonS3SSL = new(bool)
*o.FileSettings.AmazonS3SSL = true // Secure by default. *o.FileSettings.AmazonS3SSL = true // Secure by default.
} }
if o.FileSettings.AmazonS3SignV2 == nil {
o.FileSettings.AmazonS3SignV2 = new(bool)
// Signature v2 is not enabled by default.
}
if o.FileSettings.AmazonS3SSE == nil {
o.FileSettings.AmazonS3SSE = new(bool)
*o.FileSettings.AmazonS3SSE = false // Not Encrypted by default.
}
if o.FileSettings.EnableFileAttachments == nil {
o.FileSettings.EnableFileAttachments = new(bool)
*o.FileSettings.EnableFileAttachments = true
}
if o.FileSettings.EnableMobileUpload == nil {
o.FileSettings.EnableMobileUpload = new(bool)
*o.FileSettings.EnableMobileUpload = true
}
if o.FileSettings.EnableMobileDownload == nil {
o.FileSettings.EnableMobileDownload = new(bool)
*o.FileSettings.EnableMobileDownload = true
}
if o.FileSettings.MaxFileSize == nil { if o.FileSettings.MaxFileSize == nil {
o.FileSettings.MaxFileSize = new(int64) o.FileSettings.MaxFileSize = new(int64)
*o.FileSettings.MaxFileSize = 52428800 // 50 MB *o.FileSettings.MaxFileSize = 52428800 // 50 MB
} }
if len(*o.FileSettings.PublicLinkSalt) == 0 { if o.FileSettings.PublicLinkSalt == nil || len(*o.FileSettings.PublicLinkSalt) == 0 {
o.FileSettings.PublicLinkSalt = new(string) o.FileSettings.PublicLinkSalt = new(string)
*o.FileSettings.PublicLinkSalt = NewRandomString(32) *o.FileSettings.PublicLinkSalt = NewRandomString(32)
} }
@ -483,12 +587,12 @@ func (o *Config) SetDefaults() {
o.FileSettings.InitialFont = "luximbi.ttf" o.FileSettings.InitialFont = "luximbi.ttf"
} }
if len(o.EmailSettings.InviteSalt) == 0 { if o.FileSettings.Directory == "" {
o.EmailSettings.InviteSalt = NewRandomString(32) o.FileSettings.Directory = "./data/"
} }
if len(o.EmailSettings.PasswordResetSalt) == 0 { if len(o.EmailSettings.InviteSalt) == 0 {
o.EmailSettings.PasswordResetSalt = NewRandomString(32) o.EmailSettings.InviteSalt = NewRandomString(32)
} }
if o.ServiceSettings.SiteURL == nil { if o.ServiceSettings.SiteURL == nil {
@ -496,6 +600,15 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.SiteURL = SERVICE_SETTINGS_DEFAULT_SITE_URL *o.ServiceSettings.SiteURL = SERVICE_SETTINGS_DEFAULT_SITE_URL
} }
if o.ServiceSettings.LicenseFileLocation == nil {
o.ServiceSettings.LicenseFileLocation = new(string)
}
if o.ServiceSettings.EnableAPIv3 == nil {
o.ServiceSettings.EnableAPIv3 = new(bool)
*o.ServiceSettings.EnableAPIv3 = true
}
if o.ServiceSettings.EnableLinkPreviews == nil { if o.ServiceSettings.EnableLinkPreviews == nil {
o.ServiceSettings.EnableLinkPreviews = new(bool) o.ServiceSettings.EnableLinkPreviews = new(bool)
*o.ServiceSettings.EnableLinkPreviews = false *o.ServiceSettings.EnableLinkPreviews = false
@ -526,6 +639,11 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.EnforceMultifactorAuthentication = false *o.ServiceSettings.EnforceMultifactorAuthentication = false
} }
if o.ServiceSettings.EnableUserAccessTokens == nil {
o.ServiceSettings.EnableUserAccessTokens = new(bool)
*o.ServiceSettings.EnableUserAccessTokens = false
}
if o.PasswordSettings.MinimumLength == nil { if o.PasswordSettings.MinimumLength == nil {
o.PasswordSettings.MinimumLength = new(int) o.PasswordSettings.MinimumLength = new(int)
*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH *o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH
@ -594,13 +712,21 @@ func (o *Config) SetDefaults() {
if o.TeamSettings.RestrictPublicChannelCreation == nil { if o.TeamSettings.RestrictPublicChannelCreation == nil {
o.TeamSettings.RestrictPublicChannelCreation = new(string) o.TeamSettings.RestrictPublicChannelCreation = new(string)
// If this setting does not exist, assume migration from <3.6, so use management setting as default. // If this setting does not exist, assume migration from <3.6, so use management setting as default.
*o.TeamSettings.RestrictPublicChannelCreation = *o.TeamSettings.RestrictPublicChannelManagement if *o.TeamSettings.RestrictPublicChannelManagement == PERMISSIONS_CHANNEL_ADMIN {
*o.TeamSettings.RestrictPublicChannelCreation = PERMISSIONS_TEAM_ADMIN
} else {
*o.TeamSettings.RestrictPublicChannelCreation = *o.TeamSettings.RestrictPublicChannelManagement
}
} }
if o.TeamSettings.RestrictPrivateChannelCreation == nil { if o.TeamSettings.RestrictPrivateChannelCreation == nil {
o.TeamSettings.RestrictPrivateChannelCreation = new(string) o.TeamSettings.RestrictPrivateChannelCreation = new(string)
// If this setting does not exist, assume migration from <3.6, so use management setting as default. // If this setting does not exist, assume migration from <3.6, so use management setting as default.
*o.TeamSettings.RestrictPrivateChannelCreation = *o.TeamSettings.RestrictPrivateChannelManagement if *o.TeamSettings.RestrictPrivateChannelManagement == PERMISSIONS_CHANNEL_ADMIN {
*o.TeamSettings.RestrictPrivateChannelCreation = PERMISSIONS_TEAM_ADMIN
} else {
*o.TeamSettings.RestrictPrivateChannelCreation = *o.TeamSettings.RestrictPrivateChannelManagement
}
} }
if o.TeamSettings.RestrictPublicChannelDeletion == nil { if o.TeamSettings.RestrictPublicChannelDeletion == nil {
@ -615,6 +741,11 @@ func (o *Config) SetDefaults() {
*o.TeamSettings.RestrictPrivateChannelDeletion = *o.TeamSettings.RestrictPrivateChannelManagement *o.TeamSettings.RestrictPrivateChannelDeletion = *o.TeamSettings.RestrictPrivateChannelManagement
} }
if o.TeamSettings.RestrictPrivateChannelManageMembers == nil {
o.TeamSettings.RestrictPrivateChannelManageMembers = new(string)
*o.TeamSettings.RestrictPrivateChannelManageMembers = PERMISSIONS_ALL
}
if o.TeamSettings.UserStatusAwayTimeout == nil { if o.TeamSettings.UserStatusAwayTimeout == nil {
o.TeamSettings.UserStatusAwayTimeout = new(int64) o.TeamSettings.UserStatusAwayTimeout = new(int64)
*o.TeamSettings.UserStatusAwayTimeout = TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT *o.TeamSettings.UserStatusAwayTimeout = TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT
@ -680,8 +811,31 @@ func (o *Config) SetDefaults() {
*o.EmailSettings.EmailBatchingInterval = EMAIL_BATCHING_INTERVAL *o.EmailSettings.EmailBatchingInterval = EMAIL_BATCHING_INTERVAL
} }
if o.EmailSettings.EnableSMTPAuth == nil {
o.EmailSettings.EnableSMTPAuth = new(bool)
if o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE {
*o.EmailSettings.EnableSMTPAuth = false
} else {
*o.EmailSettings.EnableSMTPAuth = true
}
}
if o.EmailSettings.ConnectionSecurity == CONN_SECURITY_PLAIN {
o.EmailSettings.ConnectionSecurity = CONN_SECURITY_NONE
}
if o.EmailSettings.SkipServerCertificateVerification == nil {
o.EmailSettings.SkipServerCertificateVerification = new(bool)
*o.EmailSettings.SkipServerCertificateVerification = false
}
if o.EmailSettings.EmailNotificationContentsType == nil {
o.EmailSettings.EmailNotificationContentsType = new(string)
*o.EmailSettings.EmailNotificationContentsType = EMAIL_NOTIFICATION_CONTENTS_FULL
}
if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) { if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) {
o.SupportSettings.TermsOfServiceLink = nil *o.SupportSettings.TermsOfServiceLink = SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK
} }
if o.SupportSettings.TermsOfServiceLink == nil { if o.SupportSettings.TermsOfServiceLink == nil {
@ -690,7 +844,7 @@ func (o *Config) SetDefaults() {
} }
if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) { if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) {
o.SupportSettings.PrivacyPolicyLink = nil *o.SupportSettings.PrivacyPolicyLink = ""
} }
if o.SupportSettings.PrivacyPolicyLink == nil { if o.SupportSettings.PrivacyPolicyLink == nil {
@ -699,7 +853,7 @@ func (o *Config) SetDefaults() {
} }
if !IsSafeLink(o.SupportSettings.AboutLink) { if !IsSafeLink(o.SupportSettings.AboutLink) {
o.SupportSettings.AboutLink = nil *o.SupportSettings.AboutLink = ""
} }
if o.SupportSettings.AboutLink == nil { if o.SupportSettings.AboutLink == nil {
@ -708,7 +862,7 @@ func (o *Config) SetDefaults() {
} }
if !IsSafeLink(o.SupportSettings.HelpLink) { if !IsSafeLink(o.SupportSettings.HelpLink) {
o.SupportSettings.HelpLink = nil *o.SupportSettings.HelpLink = ""
} }
if o.SupportSettings.HelpLink == nil { if o.SupportSettings.HelpLink == nil {
@ -717,7 +871,7 @@ func (o *Config) SetDefaults() {
} }
if !IsSafeLink(o.SupportSettings.ReportAProblemLink) { if !IsSafeLink(o.SupportSettings.ReportAProblemLink) {
o.SupportSettings.ReportAProblemLink = nil *o.SupportSettings.ReportAProblemLink = ""
} }
if o.SupportSettings.ReportAProblemLink == nil { if o.SupportSettings.ReportAProblemLink == nil {
@ -725,11 +879,63 @@ func (o *Config) SetDefaults() {
*o.SupportSettings.ReportAProblemLink = SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK *o.SupportSettings.ReportAProblemLink = SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK
} }
if !IsSafeLink(o.SupportSettings.AdministratorsGuideLink) {
*o.SupportSettings.AdministratorsGuideLink = ""
}
if o.SupportSettings.AdministratorsGuideLink == nil {
o.SupportSettings.AdministratorsGuideLink = new(string)
*o.SupportSettings.AdministratorsGuideLink = SUPPORT_SETTINGS_DEFAULT_ADMINISTRATORS_GUIDE_LINK
}
if !IsSafeLink(o.SupportSettings.TroubleshootingForumLink) {
*o.SupportSettings.TroubleshootingForumLink = ""
}
if o.SupportSettings.TroubleshootingForumLink == nil {
o.SupportSettings.TroubleshootingForumLink = new(string)
*o.SupportSettings.TroubleshootingForumLink = SUPPORT_SETTINGS_DEFAULT_TROUBLESHOOTING_FORUM_LINK
}
if !IsSafeLink(o.SupportSettings.CommercialSupportLink) {
*o.SupportSettings.CommercialSupportLink = ""
}
if o.SupportSettings.CommercialSupportLink == nil {
o.SupportSettings.CommercialSupportLink = new(string)
*o.SupportSettings.CommercialSupportLink = SUPPORT_SETTINGS_DEFAULT_COMMERCIAL_SUPPORT_LINK
}
if o.SupportSettings.SupportEmail == nil { if o.SupportSettings.SupportEmail == nil {
o.SupportSettings.SupportEmail = new(string) o.SupportSettings.SupportEmail = new(string)
*o.SupportSettings.SupportEmail = SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL *o.SupportSettings.SupportEmail = SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL
} }
if o.AnnouncementSettings.EnableBanner == nil {
o.AnnouncementSettings.EnableBanner = new(bool)
*o.AnnouncementSettings.EnableBanner = false
}
if o.AnnouncementSettings.BannerText == nil {
o.AnnouncementSettings.BannerText = new(string)
*o.AnnouncementSettings.BannerText = ""
}
if o.AnnouncementSettings.BannerColor == nil {
o.AnnouncementSettings.BannerColor = new(string)
*o.AnnouncementSettings.BannerColor = ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR
}
if o.AnnouncementSettings.BannerTextColor == nil {
o.AnnouncementSettings.BannerTextColor = new(string)
*o.AnnouncementSettings.BannerTextColor = ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR
}
if o.AnnouncementSettings.AllowBannerDismissal == nil {
o.AnnouncementSettings.AllowBannerDismissal = new(bool)
*o.AnnouncementSettings.AllowBannerDismissal = true
}
if o.LdapSettings.Enable == nil { if o.LdapSettings.Enable == nil {
o.LdapSettings.Enable = new(bool) o.LdapSettings.Enable = new(bool)
*o.LdapSettings.Enable = false *o.LdapSettings.Enable = false
@ -884,7 +1090,12 @@ func (o *Config) SetDefaults() {
if o.ServiceSettings.EnableCustomEmoji == nil { if o.ServiceSettings.EnableCustomEmoji == nil {
o.ServiceSettings.EnableCustomEmoji = new(bool) o.ServiceSettings.EnableCustomEmoji = new(bool)
*o.ServiceSettings.EnableCustomEmoji = true *o.ServiceSettings.EnableCustomEmoji = false
}
if o.ServiceSettings.EnableEmojiPicker == nil {
o.ServiceSettings.EnableEmojiPicker = new(bool)
*o.ServiceSettings.EnableEmojiPicker = true
} }
if o.ServiceSettings.RestrictCustomEmojiCreation == nil { if o.ServiceSettings.RestrictCustomEmojiCreation == nil {
@ -907,18 +1118,44 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.PostEditTimeLimit = 300 *o.ServiceSettings.PostEditTimeLimit = 300
} }
if o.ClusterSettings.InterNodeListenAddress == nil {
o.ClusterSettings.InterNodeListenAddress = new(string)
*o.ClusterSettings.InterNodeListenAddress = ":8075"
}
if o.ClusterSettings.Enable == nil { if o.ClusterSettings.Enable == nil {
o.ClusterSettings.Enable = new(bool) o.ClusterSettings.Enable = new(bool)
*o.ClusterSettings.Enable = false *o.ClusterSettings.Enable = false
} }
if o.ClusterSettings.InterNodeUrls == nil { if o.ClusterSettings.ClusterName == nil {
o.ClusterSettings.InterNodeUrls = []string{} o.ClusterSettings.ClusterName = new(string)
*o.ClusterSettings.ClusterName = ""
}
if o.ClusterSettings.OverrideHostname == nil {
o.ClusterSettings.OverrideHostname = new(string)
*o.ClusterSettings.OverrideHostname = ""
}
if o.ClusterSettings.UseIpAddress == nil {
o.ClusterSettings.UseIpAddress = new(bool)
*o.ClusterSettings.UseIpAddress = true
}
if o.ClusterSettings.UseExperimentalGossip == nil {
o.ClusterSettings.UseExperimentalGossip = new(bool)
*o.ClusterSettings.UseExperimentalGossip = false
}
if o.ClusterSettings.ReadOnlyConfig == nil {
o.ClusterSettings.ReadOnlyConfig = new(bool)
*o.ClusterSettings.ReadOnlyConfig = true
}
if o.ClusterSettings.GossipPort == nil {
o.ClusterSettings.GossipPort = new(int)
*o.ClusterSettings.GossipPort = 8074
}
if o.ClusterSettings.StreamingPort == nil {
o.ClusterSettings.StreamingPort = new(int)
*o.ClusterSettings.StreamingPort = 8075
} }
if o.MetricsSettings.ListenAddress == nil { if o.MetricsSettings.ListenAddress == nil {
@ -978,12 +1215,12 @@ func (o *Config) SetDefaults() {
if o.SamlSettings.Verify == nil { if o.SamlSettings.Verify == nil {
o.SamlSettings.Verify = new(bool) o.SamlSettings.Verify = new(bool)
*o.SamlSettings.Verify = false *o.SamlSettings.Verify = true
} }
if o.SamlSettings.Encrypt == nil { if o.SamlSettings.Encrypt == nil {
o.SamlSettings.Encrypt = new(bool) o.SamlSettings.Encrypt = new(bool)
*o.SamlSettings.Encrypt = false *o.SamlSettings.Encrypt = true
} }
if o.SamlSettings.IdpUrl == nil { if o.SamlSettings.IdpUrl == nil {
@ -1056,6 +1293,15 @@ func (o *Config) SetDefaults() {
*o.SamlSettings.LocaleAttribute = SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE *o.SamlSettings.LocaleAttribute = SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE
} }
if o.TeamSettings.TeammateNameDisplay == nil {
o.TeamSettings.TeammateNameDisplay = new(string)
*o.TeamSettings.TeammateNameDisplay = SHOW_USERNAME
if *o.SamlSettings.Enable || *o.LdapSettings.Enable {
*o.TeamSettings.TeammateNameDisplay = SHOW_FULLNAME
}
}
if o.NativeAppSettings.AppDownloadLink == nil { if o.NativeAppSettings.AppDownloadLink == nil {
o.NativeAppSettings.AppDownloadLink = new(string) o.NativeAppSettings.AppDownloadLink = new(string)
*o.NativeAppSettings.AppDownloadLink = NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK *o.NativeAppSettings.AppDownloadLink = NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK
@ -1076,6 +1322,11 @@ func (o *Config) SetDefaults() {
*o.RateLimitSettings.Enable = false *o.RateLimitSettings.Enable = false
} }
if o.ServiceSettings.GoroutineHealthThreshold == nil {
o.ServiceSettings.GoroutineHealthThreshold = new(int)
*o.ServiceSettings.GoroutineHealthThreshold = -1
}
if o.RateLimitSettings.MaxBurst == nil { if o.RateLimitSettings.MaxBurst == nil {
o.RateLimitSettings.MaxBurst = new(int) o.RateLimitSettings.MaxBurst = new(int)
*o.RateLimitSettings.MaxBurst = 100 *o.RateLimitSettings.MaxBurst = 100
@ -1131,16 +1382,90 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds = 5000 *o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds = 5000
} }
if o.ServiceSettings.EnablePostSearch == nil {
o.ServiceSettings.EnablePostSearch = new(bool)
*o.ServiceSettings.EnablePostSearch = true
}
if o.ServiceSettings.EnableUserTypingMessages == nil { if o.ServiceSettings.EnableUserTypingMessages == nil {
o.ServiceSettings.EnableUserTypingMessages = new(bool) o.ServiceSettings.EnableUserTypingMessages = new(bool)
*o.ServiceSettings.EnableUserTypingMessages = true *o.ServiceSettings.EnableUserTypingMessages = true
} }
if o.ServiceSettings.EnableChannelViewedMessages == nil {
o.ServiceSettings.EnableChannelViewedMessages = new(bool)
*o.ServiceSettings.EnableChannelViewedMessages = true
}
if o.ServiceSettings.EnableUserStatuses == nil {
o.ServiceSettings.EnableUserStatuses = new(bool)
*o.ServiceSettings.EnableUserStatuses = true
}
if o.ServiceSettings.ClusterLogTimeoutMilliseconds == nil { if o.ServiceSettings.ClusterLogTimeoutMilliseconds == nil {
o.ServiceSettings.ClusterLogTimeoutMilliseconds = new(int) o.ServiceSettings.ClusterLogTimeoutMilliseconds = new(int)
*o.ServiceSettings.ClusterLogTimeoutMilliseconds = 2000 *o.ServiceSettings.ClusterLogTimeoutMilliseconds = 2000
} }
if o.ElasticsearchSettings.ConnectionUrl == nil {
o.ElasticsearchSettings.ConnectionUrl = new(string)
*o.ElasticsearchSettings.ConnectionUrl = ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL
}
if o.ElasticsearchSettings.Username == nil {
o.ElasticsearchSettings.Username = new(string)
*o.ElasticsearchSettings.Username = ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME
}
if o.ElasticsearchSettings.Password == nil {
o.ElasticsearchSettings.Password = new(string)
*o.ElasticsearchSettings.Password = ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD
}
if o.ElasticsearchSettings.EnableIndexing == nil {
o.ElasticsearchSettings.EnableIndexing = new(bool)
*o.ElasticsearchSettings.EnableIndexing = false
}
if o.ElasticsearchSettings.EnableSearching == nil {
o.ElasticsearchSettings.EnableSearching = new(bool)
*o.ElasticsearchSettings.EnableSearching = false
}
if o.ElasticsearchSettings.Sniff == nil {
o.ElasticsearchSettings.Sniff = new(bool)
*o.ElasticsearchSettings.Sniff = true
}
if o.ElasticsearchSettings.PostIndexReplicas == nil {
o.ElasticsearchSettings.PostIndexReplicas = new(int)
*o.ElasticsearchSettings.PostIndexReplicas = ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS
}
if o.ElasticsearchSettings.PostIndexShards == nil {
o.ElasticsearchSettings.PostIndexShards = new(int)
*o.ElasticsearchSettings.PostIndexShards = ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS
}
if o.DataRetentionSettings.Enable == nil {
o.DataRetentionSettings.Enable = new(bool)
*o.DataRetentionSettings.Enable = false
}
if o.JobSettings.RunJobs == nil {
o.JobSettings.RunJobs = new(bool)
*o.JobSettings.RunJobs = true
}
if o.JobSettings.RunScheduler == nil {
o.JobSettings.RunScheduler = new(bool)
*o.JobSettings.RunScheduler = true
}
if o.PluginSettings.Plugins == nil {
o.PluginSettings.Plugins = make(map[string]interface{})
}
o.defaultWebrtcSettings() o.defaultWebrtcSettings()
} }
@ -1184,6 +1509,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "")
} }
if !(*o.TeamSettings.TeammateNameDisplay == SHOW_FULLNAME || *o.TeamSettings.TeammateNameDisplay == SHOW_NICKNAME_FULLNAME || *o.TeamSettings.TeammateNameDisplay == SHOW_USERNAME) {
return NewLocAppError("Config.IsValid", "model.config.is_valid.teammate_name_display.app_error", nil, "")
}
if len(o.SqlSettings.AtRestEncryptKey) < 32 { if len(o.SqlSettings.AtRestEncryptKey) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "")
} }
@ -1196,6 +1525,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "")
} }
if *o.SqlSettings.QueryTimeout <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest)
}
if len(o.SqlSettings.DataSource) == 0 { if len(o.SqlSettings.DataSource) == 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "")
} }
@ -1212,30 +1545,6 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "")
} }
if o.FileSettings.PreviewHeight < 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_height.app_error", nil, "")
}
if o.FileSettings.PreviewWidth <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_width.app_error", nil, "")
}
if o.FileSettings.ProfileHeight <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_height.app_error", nil, "")
}
if o.FileSettings.ProfileWidth <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_width.app_error", nil, "")
}
if o.FileSettings.ThumbnailHeight <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_height.app_error", nil, "")
}
if o.FileSettings.ThumbnailWidth <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "")
}
if len(*o.FileSettings.PublicLinkSalt) < 32 { if len(*o.FileSettings.PublicLinkSalt) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "")
} }
@ -1248,10 +1557,6 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "")
} }
if len(o.EmailSettings.PasswordResetSalt) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "")
}
if *o.EmailSettings.EmailBatchingBufferSize <= 0 { if *o.EmailSettings.EmailBatchingBufferSize <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "")
} }
@ -1260,6 +1565,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "")
} }
if !(*o.EmailSettings.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_FULL || *o.EmailSettings.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_GENERIC) {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_notification_contents_type.app_error", nil, "")
}
if o.RateLimitSettings.MemoryStoreSize <= 0 { if o.RateLimitSettings.MemoryStoreSize <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "")
} }
@ -1376,6 +1685,16 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "") return NewLocAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "")
} }
if *o.ElasticsearchSettings.EnableIndexing {
if len(*o.ElasticsearchSettings.ConnectionUrl) == 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.elastic_search.connection_url.app_error", nil, "")
}
}
if *o.ElasticsearchSettings.EnableSearching && !*o.ElasticsearchSettings.EnableIndexing {
return NewLocAppError("Config.IsValid", "model.config.is_valid.elastic_search.enable_searching.app_error", nil, "")
}
return nil return nil
} }
@ -1398,7 +1717,6 @@ func (o *Config) Sanitize() {
} }
o.EmailSettings.InviteSalt = FAKE_SETTING o.EmailSettings.InviteSalt = FAKE_SETTING
o.EmailSettings.PasswordResetSalt = FAKE_SETTING
if len(o.EmailSettings.SMTPPassword) > 0 { if len(o.EmailSettings.SMTPPassword) > 0 {
o.EmailSettings.SMTPPassword = FAKE_SETTING o.EmailSettings.SMTPPassword = FAKE_SETTING
} }
@ -1413,6 +1731,12 @@ func (o *Config) Sanitize() {
for i := range o.SqlSettings.DataSourceReplicas { for i := range o.SqlSettings.DataSourceReplicas {
o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING
} }
for i := range o.SqlSettings.DataSourceSearchReplicas {
o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING
}
*o.ElasticsearchSettings.Password = FAKE_SETTING
} }
func (o *Config) defaultWebrtcSettings() { func (o *Config) defaultWebrtcSettings() {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -34,7 +34,7 @@ func (emoji *Emoji) IsValid() *AppError {
return NewLocAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "") return NewLocAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "")
} }
if len(emoji.Name) == 0 || len(emoji.Name) > 64 { if len(emoji.Name) == 0 || len(emoji.Name) > 64 || !IsValidAlphaNumHyphenUnderscore(emoji.Name, false) {
return NewLocAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "") return NewLocAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "")
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package oauthgitlab package oauthgitlab

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -6,10 +6,9 @@ package model
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http"
"regexp" "regexp"
"strings"
) )
const ( const (
@ -81,35 +80,36 @@ func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook {
func (o *IncomingWebhook) IsValid() *AppError { func (o *IncomingWebhook) IsValid() *AppError {
if len(o.Id) != 26 { if len(o.Id) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "") return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "", http.StatusBadRequest)
} }
if o.CreateAt == 0 { if o.CreateAt == 0 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id) return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }
if o.UpdateAt == 0 { if o.UpdateAt == 0 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id) return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }
if len(o.UserId) != 26 { if len(o.UserId) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "") return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.ChannelId) != 26 { if len(o.ChannelId) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "") return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.TeamId) != 26 { if len(o.TeamId) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "") return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.DisplayName) > 64 { if len(o.DisplayName) > 64 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "") return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.Description) > 128 { if len(o.Description) > 128 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "") return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "", http.StatusBadRequest)
} }
return nil return nil
@ -193,39 +193,6 @@ func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) {
} }
} }
// To mention @channel via a webhook in Slack, the message should contain
// <!channel>, as explained at the bottom of this article:
// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
func expandAnnouncement(text string) string {
c1 := "<!channel>"
c2 := "@channel"
if strings.Contains(text, c1) {
return strings.Replace(text, c1, c2, -1)
}
return text
}
// Expand announcements in incoming webhooks from Slack. Those announcements
// can be found in the text attribute, or in the pretext, text, title and value
// attributes of the attachment structure. The Slack attachment structure is
// documented here: https://api.slack.com/docs/attachments
func expandAnnouncements(i *IncomingWebhookRequest) {
i.Text = expandAnnouncement(i.Text)
for _, attachment := range i.Attachments {
attachment.Pretext = expandAnnouncement(attachment.Pretext)
attachment.Text = expandAnnouncement(attachment.Text)
attachment.Title = expandAnnouncement(attachment.Title)
for _, field := range attachment.Fields {
if field.Value != nil {
// Ensure the value is set to a string if it is set
field.Value = expandAnnouncement(fmt.Sprintf("%v", field.Value))
}
}
}
}
func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.ReadFrom(data) buf.ReadFrom(data)
@ -241,7 +208,8 @@ func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
} }
} }
expandAnnouncements(o) o.Text = ExpandAnnouncement(o.Text)
o.Attachments = ProcessSlackAttachments(o.Attachments)
return o return o
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,100 +1,117 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
import ( import (
"fmt" "encoding/json"
"time" "io"
"net/http"
) )
type TaskFunc func() const (
JOB_TYPE_DATA_RETENTION = "data_retention"
JOB_TYPE_ELASTICSEARCH_POST_INDEXING = "elasticsearch_post_indexing"
type ScheduledTask struct { JOB_STATUS_PENDING = "pending"
Name string `json:"name"` JOB_STATUS_IN_PROGRESS = "in_progress"
Interval time.Duration `json:"interval"` JOB_STATUS_SUCCESS = "success"
Recurring bool `json:"recurring"` JOB_STATUS_ERROR = "error"
function TaskFunc JOB_STATUS_CANCEL_REQUESTED = "cancel_requested"
timer *time.Timer JOB_STATUS_CANCELED = "canceled"
)
type Job struct {
Id string `json:"id"`
Type string `json:"type"`
Priority int64 `json:"priority"`
CreateAt int64 `json:"create_at"`
StartAt int64 `json:"start_at"`
LastActivityAt int64 `json:"last_activity_at"`
Status string `json:"status"`
Progress int64 `json:"progress"`
Data map[string]interface{} `json:"data"`
} }
var tasks = make(map[string]*ScheduledTask) func (j *Job) IsValid() *AppError {
if len(j.Id) != 26 {
func addTask(task *ScheduledTask) { return NewAppError("Job.IsValid", "model.job.is_valid.id.app_error", nil, "id="+j.Id, http.StatusBadRequest)
tasks[task.Name] = task
}
func removeTaskByName(name string) {
delete(tasks, name)
}
func GetTaskByName(name string) *ScheduledTask {
if task, ok := tasks[name]; ok {
return task
} }
if j.CreateAt == 0 {
return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Type {
case JOB_TYPE_DATA_RETENTION:
case JOB_TYPE_ELASTICSEARCH_POST_INDEXING:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Status {
case JOB_STATUS_PENDING:
case JOB_STATUS_IN_PROGRESS:
case JOB_STATUS_SUCCESS:
case JOB_STATUS_ERROR:
case JOB_STATUS_CANCEL_REQUESTED:
case JOB_STATUS_CANCELED:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
return nil return nil
} }
func GetAllTasks() *map[string]*ScheduledTask { func (js *Job) ToJson() string {
return &tasks if b, err := json.Marshal(js); err != nil {
} return ""
} else {
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask { return string(b)
task := &ScheduledTask{
Name: name,
Interval: timeToExecution,
Recurring: false,
function: function,
} }
}
taskRunner := func() { func JobFromJson(data io.Reader) *Job {
go task.function() var job Job
removeTaskByName(task.Name) if err := json.NewDecoder(data).Decode(&job); err == nil {
return &job
} else {
return nil
} }
task.timer = time.AfterFunc(timeToExecution, taskRunner)
addTask(task)
return task
} }
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask { func JobsToJson(jobs []*Job) string {
task := &ScheduledTask{ if b, err := json.Marshal(jobs); err != nil {
Name: name, return ""
Interval: interval, } else {
Recurring: true, return string(b)
function: function,
} }
}
taskRecurer := func() { func JobsFromJson(data io.Reader) []*Job {
go task.function() var jobs []*Job
task.timer.Reset(task.Interval) if err := json.NewDecoder(data).Decode(&jobs); err == nil {
return jobs
} else {
return nil
} }
task.timer = time.AfterFunc(interval, taskRecurer)
addTask(task)
return task
} }
func (task *ScheduledTask) Cancel() { func (js *Job) DataToJson() string {
task.timer.Stop() if b, err := json.Marshal(js.Data); err != nil {
removeTaskByName(task.Name) return ""
} else {
return string(b)
}
} }
// Executes the task immediatly. A recurring task will be run regularally after interval. type Worker interface {
func (task *ScheduledTask) Execute() { Run()
task.function() Stop()
task.timer.Reset(task.Interval) JobChannel() chan<- Job
} }
func (task *ScheduledTask) String() string { type Scheduler interface {
return fmt.Sprintf( Run()
"%s\nInterval: %s\nRecurring: %t\n", Stop()
task.Name,
task.Interval.String(),
task.Recurring,
)
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -37,36 +37,42 @@ type Customer struct {
} }
type Features struct { type Features struct {
Users *int `json:"users"` Users *int `json:"users"`
LDAP *bool `json:"ldap"` LDAP *bool `json:"ldap"`
MFA *bool `json:"mfa"` MFA *bool `json:"mfa"`
GoogleOAuth *bool `json:"google_oauth"` GoogleOAuth *bool `json:"google_oauth"`
Office365OAuth *bool `json:"office365_oauth"` Office365OAuth *bool `json:"office365_oauth"`
Compliance *bool `json:"compliance"` Compliance *bool `json:"compliance"`
Cluster *bool `json:"cluster"` Cluster *bool `json:"cluster"`
Metrics *bool `json:"metrics"` Metrics *bool `json:"metrics"`
CustomBrand *bool `json:"custom_brand"` CustomBrand *bool `json:"custom_brand"`
MHPNS *bool `json:"mhpns"` MHPNS *bool `json:"mhpns"`
SAML *bool `json:"saml"` SAML *bool `json:"saml"`
PasswordRequirements *bool `json:"password_requirements"` PasswordRequirements *bool `json:"password_requirements"`
Elasticsearch *bool `json:"elastic_search"`
Announcement *bool `json:"announcement"`
EmailNotificationContents *bool `json:"email_notification_contents"`
// after we enabled more features for webrtc we'll need to control them with this // after we enabled more features for webrtc we'll need to control them with this
FutureFeatures *bool `json:"future_features"` FutureFeatures *bool `json:"future_features"`
} }
func (f *Features) ToMap() map[string]interface{} { func (f *Features) ToMap() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"ldap": *f.LDAP, "ldap": *f.LDAP,
"mfa": *f.MFA, "mfa": *f.MFA,
"google": *f.GoogleOAuth, "google": *f.GoogleOAuth,
"office365": *f.Office365OAuth, "office365": *f.Office365OAuth,
"compliance": *f.Compliance, "compliance": *f.Compliance,
"cluster": *f.Cluster, "cluster": *f.Cluster,
"metrics": *f.Metrics, "metrics": *f.Metrics,
"custom_brand": *f.CustomBrand, "custom_brand": *f.CustomBrand,
"mhpns": *f.MHPNS, "mhpns": *f.MHPNS,
"saml": *f.SAML, "saml": *f.SAML,
"password": *f.PasswordRequirements, "password": *f.PasswordRequirements,
"future": *f.FutureFeatures, "elastic_search": *f.Elasticsearch,
"email_notification_contents": *f.EmailNotificationContents,
"future": *f.FutureFeatures,
} }
} }
@ -135,6 +141,21 @@ func (f *Features) SetDefaults() {
f.PasswordRequirements = new(bool) f.PasswordRequirements = new(bool)
*f.PasswordRequirements = *f.FutureFeatures *f.PasswordRequirements = *f.FutureFeatures
} }
if f.Elasticsearch == nil {
f.Elasticsearch = new(bool)
*f.Elasticsearch = *f.FutureFeatures
}
if f.Announcement == nil {
f.Announcement = new(bool)
*f.Announcement = true
}
if f.EmailNotificationContents == nil {
f.EmailNotificationContents = new(bool)
*f.EmailNotificationContents = *f.FutureFeatures
}
} }
func (l *License) IsExpired() bool { func (l *License) IsExpired() bool {

View File

@ -0,0 +1,34 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type MfaSecret struct {
Secret string `json:"secret"`
QRCode string `json:"qr_code"`
}
func (me *MfaSecret) ToJson() string {
b, err := json.Marshal(me)
if err != nil {
return ""
} else {
return string(b)
}
}
func MfaSecretFromJson(data io.Reader) *MfaSecret {
decoder := json.NewDecoder(data)
var me MfaSecret
err := decoder.Decode(&me)
if err == nil {
return &me
} else {
return nil
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http"
"unicode/utf8" "unicode/utf8"
) )
@ -15,6 +16,7 @@ const (
OAUTH_ACTION_LOGIN = "login" OAUTH_ACTION_LOGIN = "login"
OAUTH_ACTION_EMAIL_TO_SSO = "email_to_sso" OAUTH_ACTION_EMAIL_TO_SSO = "email_to_sso"
OAUTH_ACTION_SSO_TO_EMAIL = "sso_to_email" OAUTH_ACTION_SSO_TO_EMAIL = "sso_to_email"
OAUTH_ACTION_MOBILE = "mobile"
) )
type OAuthApp struct { type OAuthApp struct {
@ -36,50 +38,50 @@ type OAuthApp struct {
func (a *OAuthApp) IsValid() *AppError { func (a *OAuthApp) IsValid() *AppError {
if len(a.Id) != 26 { if len(a.Id) != 26 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "") return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "", http.StatusBadRequest)
} }
if a.CreateAt == 0 { if a.CreateAt == 0 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if a.UpdateAt == 0 { if a.UpdateAt == 0 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.CreatorId) != 26 { if len(a.CreatorId) != 26 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 { if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.Name) == 0 || len(a.Name) > 64 { if len(a.Name) == 0 || len(a.Name) > 64 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 { if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
for _, callback := range a.CallbackUrls { for _, callback := range a.CallbackUrls {
if !IsValidHttpUrl(callback) { if !IsValidHttpUrl(callback) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "") return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
} }
} }
if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if utf8.RuneCountInString(a.Description) > 512 { if utf8.RuneCountInString(a.Description) > 512 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
if len(a.IconURL) > 0 { if len(a.IconURL) > 0 {
if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id) return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -41,6 +42,7 @@ type OutgoingWebhookPayload struct {
PostId string `json:"post_id"` PostId string `json:"post_id"`
Text string `json:"text"` Text string `json:"text"`
TriggerWord string `json:"trigger_word"` TriggerWord string `json:"trigger_word"`
FileIds string `json:"file_ids"`
} }
func (o *OutgoingWebhookPayload) ToJSON() string { func (o *OutgoingWebhookPayload) ToJSON() string {
@ -65,6 +67,7 @@ func (o *OutgoingWebhookPayload) ToFormValues() string {
v.Set("post_id", o.PostId) v.Set("post_id", o.PostId)
v.Set("text", o.Text) v.Set("text", o.Text)
v.Set("trigger_word", o.TriggerWord) v.Set("trigger_word", o.TriggerWord)
v.Set("file_ids", o.FileIds)
return v.Encode() return v.Encode()
} }
@ -112,69 +115,69 @@ func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook {
func (o *OutgoingWebhook) IsValid() *AppError { func (o *OutgoingWebhook) IsValid() *AppError {
if len(o.Id) != 26 { if len(o.Id) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.Token) != 26 { if len(o.Token) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest)
} }
if o.CreateAt == 0 { if o.CreateAt == 0 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id) return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }
if o.UpdateAt == 0 { if o.UpdateAt == 0 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id) return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
} }
if len(o.CreatorId) != 26 { if len(o.CreatorId) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 { if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.TeamId) != 26 { if len(o.TeamId) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
} }
if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 { if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.TriggerWords) != 0 { if len(o.TriggerWords) != 0 {
for _, triggerWord := range o.TriggerWords { for _, triggerWord := range o.TriggerWords {
if len(triggerWord) == 0 { if len(triggerWord) == 0 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest)
} }
} }
} }
if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 { if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
} }
for _, callback := range o.CallbackURLs { for _, callback := range o.CallbackURLs {
if !IsValidHttpUrl(callback) { if !IsValidHttpUrl(callback) {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest)
} }
} }
if len(o.DisplayName) > 64 { if len(o.DisplayName) > 64 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.Description) > 128 { if len(o.Description) > 128 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest)
} }
if len(o.ContentType) > 128 { if len(o.ContentType) > 128 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
} }
if o.TriggerWhen > 1 { if o.TriggerWhen > 1 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
} }
return nil return nil
@ -197,8 +200,8 @@ func (o *OutgoingWebhook) PreUpdate() {
o.UpdateAt = GetMillis() o.UpdateAt = GetMillis()
} }
func (o *OutgoingWebhook) HasTriggerWord(word string) bool { func (o *OutgoingWebhook) TriggerWordExactMatch(word string) bool {
if len(o.TriggerWords) == 0 || len(word) == 0 { if len(word) == 0 {
return false return false
} }
@ -212,7 +215,7 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
} }
func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
if len(o.TriggerWords) == 0 || len(word) == 0 { if len(word) == 0 {
return false return false
} }
@ -224,3 +227,27 @@ func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
return false return false
} }
func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) {
if len(word) == 0 {
return
}
if isExactMatch {
for _, trigger := range o.TriggerWords {
if trigger == word {
triggerWord = trigger
break
}
}
} else {
for _, trigger := range o.TriggerWords {
if strings.HasPrefix(word, trigger) {
triggerWord = trigger
break
}
}
}
return triggerWord
}

View File

@ -1,37 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
const (
PASSWORD_RECOVERY_CODE_SIZE = 128
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
)
type PasswordRecovery struct {
UserId string
Code string
CreateAt int64
}
func (p *PasswordRecovery) IsValid() *AppError {
if len(p.UserId) != 26 {
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.user_id.app_error", nil, "")
}
if len(p.Code) != PASSWORD_RECOVERY_CODE_SIZE {
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.code.app_error", nil, "")
}
if p.CreateAt == 0 {
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.create_at.app_error", nil, "")
}
return nil
}
func (p *PasswordRecovery) PreSave() {
p.Code = NewRandomString(PASSWORD_RECOVERY_CODE_SIZE)
p.CreateAt = GetMillis()
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -38,6 +38,7 @@ type Post struct {
UpdateAt int64 `json:"update_at"` UpdateAt int64 `json:"update_at"`
EditAt int64 `json:"edit_at"` EditAt int64 `json:"edit_at"`
DeleteAt int64 `json:"delete_at"` DeleteAt int64 `json:"delete_at"`
IsPinned bool `json:"is_pinned"`
UserId string `json:"user_id"` UserId string `json:"user_id"`
ChannelId string `json:"channel_id"` ChannelId string `json:"channel_id"`
RootId string `json:"root_id"` RootId string `json:"root_id"`
@ -53,6 +54,20 @@ type Post struct {
HasReactions bool `json:"has_reactions,omitempty"` HasReactions bool `json:"has_reactions,omitempty"`
} }
type PostPatch struct {
IsPinned *bool `json:"is_pinned"`
Message *string `json:"message"`
Props *StringInterface `json:"props"`
FileIds *StringArray `json:"file_ids"`
HasReactions *bool `json:"has_reactions"`
}
type PostForIndexing struct {
Post
TeamId string `json:"team_id"`
ParentCreateAt *int64 `json:"parent_create_at"`
}
func (o *Post) ToJson() string { func (o *Post) ToJson() string {
b, err := json.Marshal(o) b, err := json.Marshal(o)
if err != nil { if err != nil {
@ -189,3 +204,45 @@ func (o *Post) AddProp(key string, value interface{}) {
func (o *Post) IsSystemMessage() bool { func (o *Post) IsSystemMessage() bool {
return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX
} }
func (p *Post) Patch(patch *PostPatch) {
if patch.IsPinned != nil {
p.IsPinned = *patch.IsPinned
}
if patch.Message != nil {
p.Message = *patch.Message
}
if patch.Props != nil {
p.Props = *patch.Props
}
if patch.FileIds != nil {
p.FileIds = *patch.FileIds
}
if patch.HasReactions != nil {
p.HasReactions = *patch.HasReactions
}
}
func (o *PostPatch) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func PostPatchFromJson(data io.Reader) *PostPatch {
decoder := json.NewDecoder(data)
var post PostPatch
err := decoder.Decode(&post)
if err != nil {
return nil
}
return &post
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -16,14 +16,10 @@ const (
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step" PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings" PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post" PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post"
PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel"
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews" PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
PREFERENCE_NAME_DISPLAY_NAME_FORMAT = "name_format"
PREFERENCE_VALUE_DISPLAY_NAME_NICKNAME = "nickname_full_name"
PREFERENCE_VALUE_DISPLAY_NAME_FULL = "full_name"
PREFERENCE_VALUE_DISPLAY_NAME_USERNAME = "username"
PREFERENCE_DEFAULT_DISPLAY_NAME_FORMAT = PREFERENCE_VALUE_DISPLAY_NAME_USERNAME
PREFERENCE_CATEGORY_THEME = "theme" PREFERENCE_CATEGORY_THEME = "theme"
// the name for theme props is the team id // the name for theme props is the team id
@ -37,7 +33,9 @@ const (
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval" PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
PREFERENCE_DEFAULT_EMAIL_INTERVAL = "30" // default to match the interval of the "immediate" setting (ie 30 seconds)
PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS = "30" // the "immediate" setting is actually 30s
PREFERENCE_EMAIL_INTERVAL_BATCHING_SECONDS = "900" // fifteen minutes is 900 seconds
) )
type Preference struct { type Preference struct {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -36,6 +36,10 @@ type PushNotification struct {
ChannelId string `json:"channel_id"` ChannelId string `json:"channel_id"`
ChannelName string `json:"channel_name"` ChannelName string `json:"channel_name"`
Type string `json:"type"` Type string `json:"type"`
SenderId string `json:"sender_id"`
OverrideUsername string `json:"override_username"`
OverrideIconUrl string `json:"override_icon_url"`
FromWebhook string `json:"from_webhook"`
} }
func (me *PushNotification) ToJson() string { func (me *PushNotification) ToJson() string {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -6,6 +6,7 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"regexp"
) )
type Reaction struct { type Reaction struct {
@ -60,7 +61,9 @@ func (o *Reaction) IsValid() *AppError {
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId) return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId)
} }
if len(o.EmojiName) == 0 || len(o.EmojiName) > 64 { validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`)
if len(o.EmojiName) == 0 || len(o.EmojiName) > 64 || !validName.MatchString(o.EmojiName) {
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName) return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName)
} }

View File

@ -1,8 +1,13 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
import (
"encoding/json"
"io"
)
const ( const (
USER_AUTH_SERVICE_SAML = "saml" USER_AUTH_SERVICE_SAML = "saml"
USER_AUTH_SERVICE_SAML_TEXT = "With SAML" USER_AUTH_SERVICE_SAML_TEXT = "With SAML"
@ -16,3 +21,29 @@ type SamlAuthRequest struct {
URL string URL string
RelayState string RelayState string
} }
type SamlCertificateStatus struct {
IdpCertificateFile bool `json:"idp_certificate_file"`
PrivateKeyFile bool `json:"private_key_file"`
PublicCertificateFile bool `json:"public_certificate_file"`
}
func (s *SamlCertificateStatus) ToJson() string {
b, err := json.Marshal(s)
if err != nil {
return ""
} else {
return string(b)
}
}
func SamlCertificateStatusFromJson(data io.Reader) *SamlCertificateStatus {
decoder := json.NewDecoder(data)
var status SamlCertificateStatus
err := decoder.Decode(&status)
if err == nil {
return &status
} else {
return nil
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"fmt"
"sync"
"time"
)
type TaskFunc func()
type ScheduledTask struct {
Name string `json:"name"`
Interval time.Duration `json:"interval"`
Recurring bool `json:"recurring"`
function TaskFunc
timer *time.Timer
}
var taskMutex = sync.Mutex{}
var tasks = make(map[string]*ScheduledTask)
func addTask(task *ScheduledTask) {
taskMutex.Lock()
defer taskMutex.Unlock()
tasks[task.Name] = task
}
func removeTaskByName(name string) {
taskMutex.Lock()
defer taskMutex.Unlock()
delete(tasks, name)
}
func GetTaskByName(name string) *ScheduledTask {
taskMutex.Lock()
defer taskMutex.Unlock()
if task, ok := tasks[name]; ok {
return task
}
return nil
}
func GetAllTasks() *map[string]*ScheduledTask {
taskMutex.Lock()
defer taskMutex.Unlock()
return &tasks
}
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: timeToExecution,
Recurring: false,
function: function,
}
taskRunner := func() {
go task.function()
removeTaskByName(task.Name)
}
task.timer = time.AfterFunc(timeToExecution, taskRunner)
addTask(task)
return task
}
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: interval,
Recurring: true,
function: function,
}
taskRecurer := func() {
go task.function()
task.timer.Reset(task.Interval)
}
task.timer = time.AfterFunc(interval, taskRecurer)
addTask(task)
return task
}
func (task *ScheduledTask) Cancel() {
task.timer.Stop()
removeTaskByName(task.Name)
}
// Executes the task immediatly. A recurring task will be run regularally after interval.
func (task *ScheduledTask) Execute() {
task.function()
task.timer.Reset(task.Interval)
}
func (task *ScheduledTask) String() string {
return fmt.Sprintf(
"%s\nInterval: %s\nRecurring: %t\n",
task.Name,
task.Interval.String(),
task.Recurring,
)
}

View File

@ -1,9 +1,10 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
import ( import (
"encoding/json"
"regexp" "regexp"
"strings" "strings"
) )
@ -19,6 +20,15 @@ type SearchParams struct {
OrTerms bool OrTerms bool
} }
func (o *SearchParams) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
var searchFlags = [...]string{"from", "channel", "in"} var searchFlags = [...]string{"from", "channel", "in"}
func splitWordsNoQuotes(text string) []string { func splitWordsNoQuotes(text string) []string {
@ -165,7 +175,7 @@ func ParseSearchParams(text string) []*SearchParams {
if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0) { if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0) {
paramsList = append(paramsList, &SearchParams{ paramsList = append(paramsList, &SearchParams{
Terms: "", Terms: "",
IsHashtag: true, IsHashtag: false,
InChannels: inChannels, InChannels: inChannels,
FromUsers: fromUsers, FromUsers: fromUsers,
}) })

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -10,11 +10,17 @@ import (
) )
const ( const (
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN" SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
SESSION_CACHE_SIZE = 35000 SESSION_COOKIE_USER = "MMUSERID"
SESSION_PROP_PLATFORM = "platform" SESSION_CACHE_SIZE = 35000
SESSION_PROP_OS = "os" SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_BROWSER = "browser" SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
SESSION_PROP_TYPE = "type"
SESSION_PROP_USER_ACCESS_TOKEN_ID = "user_access_token_id"
SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken"
SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years
) )
type Session struct { type Session struct {
@ -56,7 +62,9 @@ func (me *Session) PreSave() {
me.Id = NewId() me.Id = NewId()
} }
me.Token = NewId() if me.Token == "" {
me.Token = NewId()
}
me.CreateAt = GetMillis() me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt me.LastActivityAt = me.CreateAt

View File

@ -1,8 +1,13 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
import (
"fmt"
"strings"
)
type SlackAttachment struct { type SlackAttachment struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Fallback string `json:"fallback"` Fallback string `json:"fallback"`
@ -27,3 +32,48 @@ type SlackAttachmentField struct {
Value interface{} `json:"value"` Value interface{} `json:"value"`
Short bool `json:"short"` Short bool `json:"short"`
} }
// To mention @channel via a webhook in Slack, the message should contain
// <!channel>, as explained at the bottom of this article:
// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
func ExpandAnnouncement(text string) string {
c1 := "<!channel>"
c2 := "@channel"
if strings.Contains(text, c1) {
return strings.Replace(text, c1, c2, -1)
}
return text
}
// Expand announcements in incoming webhooks from Slack. Those announcements
// can be found in the text attribute, or in the pretext, text, title and value
// attributes of the attachment structure. The Slack attachment structure is
// documented here: https://api.slack.com/docs/attachments
func ProcessSlackAttachments(a []*SlackAttachment) []*SlackAttachment {
var nonNilAttachments []*SlackAttachment
for _, attachment := range a {
if attachment == nil {
continue
}
nonNilAttachments = append(nonNilAttachments, attachment)
attachment.Pretext = ExpandAnnouncement(attachment.Pretext)
attachment.Text = ExpandAnnouncement(attachment.Text)
attachment.Title = ExpandAnnouncement(attachment.Title)
var nonNilFields []*SlackAttachmentField
for _, field := range attachment.Fields {
if field == nil {
continue
}
nonNilFields = append(nonNilFields, field)
if field.Value != nil {
// Ensure the value is set to a string if it is set
field.Value = ExpandAnnouncement(fmt.Sprintf("%v", field.Value))
}
}
attachment.Fields = nonNilFields
}
return nonNilAttachments
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model
@ -22,7 +22,7 @@ type Status struct {
Status string `json:"status"` Status string `json:"status"`
Manual bool `json:"manual"` Manual bool `json:"manual"`
LastActivityAt int64 `json:"last_activity_at"` LastActivityAt int64 `json:"last_activity_at"`
ActiveChannel string `json:"active_channel" db:"-"` ActiveChannel string `json:"-" db:"-"`
} }
func (o *Status) ToJson() string { func (o *Status) ToJson() string {
@ -45,6 +45,26 @@ func StatusFromJson(data io.Reader) *Status {
} }
} }
func StatusListToJson(u []*Status) string {
b, err := json.Marshal(u)
if err != nil {
return ""
} else {
return string(b)
}
}
func StatusListFromJson(data io.Reader) []*Status {
decoder := json.NewDecoder(data)
var statuses []*Status
err := decoder.Decode(&statuses)
if err == nil {
return statuses
} else {
return nil
}
}
func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} { func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} {
interfaceMap := map[string]interface{}{} interfaceMap := map[string]interface{}{}
for _, s := range statusMap { for _, s := range statusMap {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package model package model

View File

@ -0,0 +1,62 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type SwitchRequest struct {
CurrentService string `json:"current_service"`
NewService string `json:"new_service"`
Email string `json:"email"`
Password string `json:"password"`
NewPassword string `json:"new_password"`
MfaCode string `json:"mfa_code"`
LdapId string `json:"ldap_id"`
}
func (o *SwitchRequest) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func SwitchRequestFromJson(data io.Reader) *SwitchRequest {
decoder := json.NewDecoder(data)
var o SwitchRequest
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *SwitchRequest) EmailToOAuth() bool {
return o.CurrentService == USER_AUTH_SERVICE_EMAIL &&
(o.NewService == USER_AUTH_SERVICE_SAML ||
o.NewService == USER_AUTH_SERVICE_GITLAB ||
o.NewService == SERVICE_GOOGLE ||
o.NewService == SERVICE_OFFICE365)
}
func (o *SwitchRequest) OAuthToEmail() bool {
return (o.CurrentService == USER_AUTH_SERVICE_SAML ||
o.CurrentService == USER_AUTH_SERVICE_GITLAB ||
o.CurrentService == SERVICE_GOOGLE ||
o.CurrentService == SERVICE_OFFICE365) && o.NewService == USER_AUTH_SERVICE_EMAIL
}
func (o *SwitchRequest) EmailToLdap() bool {
return o.CurrentService == USER_AUTH_SERVICE_EMAIL && o.NewService == USER_AUTH_SERVICE_LDAP
}
func (o *SwitchRequest) LdapToEmail() bool {
return o.CurrentService == USER_AUTH_SERVICE_LDAP && o.NewService == USER_AUTH_SERVICE_EMAIL
}

Some files were not shown because too many files have changed in this diff Show More