diff --git a/bridge/mattermost/mattermost.go b/bridge/mattermost/mattermost.go index 7f7340b6..8328f0b0 100644 --- a/bridge/mattermost/mattermost.go +++ b/bridge/mattermost/mattermost.go @@ -153,7 +153,7 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { for message := range b.mc.MessageChan { // do not post our own messages back to irc // only listen to message from our team - if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.TeamId == b.TeamId { + if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId { flog.Debugf("Receiving from matterclient %#v", message) m := &MMMessage{} m.Username = message.Username diff --git a/matterclient/matterclient.go b/matterclient/matterclient.go index 38be9b00..90c8d1a4 100644 --- a/matterclient/matterclient.go +++ b/matterclient/matterclient.go @@ -264,7 +264,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { } rmsg.Username = m.GetUser(data.UserId).Username rmsg.Channel = m.GetChannelName(data.ChannelId) - rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) + rmsg.Team = m.GetTeamName(rmsg.Raw.Data["team_id"].(string)) // direct message if rmsg.Raw.Data["channel_type"] == "D" { rmsg.Channel = m.GetUser(data.UserId).Username @@ -275,7 +275,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { } func (m *MMClient) UpdateUsers() error { - mmusers, err := m.Client.GetProfilesForDirectMessageList(m.Team.Id) + mmusers, err := m.Client.GetProfiles(0, 1000, "") if err != nil { return errors.New(err.DetailedError) } @@ -305,7 +305,7 @@ func (m *MMClient) GetChannelName(channelId string) string { m.RLock() defer m.RUnlock() for _, t := range m.OtherTeams { - for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { + for _, channel := range append(*t.Channels, *t.MoreChannels...) { if channel.Id == channelId { return channel.Name } @@ -322,7 +322,7 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { } for _, t := range m.OtherTeams { if t.Id == teamId { - for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { + for _, channel := range append(*t.Channels, *t.MoreChannels...) { if channel.Name == name { return channel.Id } @@ -336,7 +336,7 @@ func (m *MMClient) GetChannelHeader(channelId string) string { m.RLock() defer m.RUnlock() for _, t := range m.OtherTeams { - for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { + for _, channel := range append(*t.Channels, *t.MoreChannels...) { if channel.Id == channelId { return channel.Header } @@ -354,7 +354,7 @@ func (m *MMClient) PostMessage(channelId string, text string) { func (m *MMClient) JoinChannel(channelId string) error { m.RLock() defer m.RUnlock() - for _, c := range m.Team.Channels.Channels { + for _, c := range *m.Team.Channels { if c.Id == channelId { m.log.Debug("Not joining ", channelId, " already joined.") return nil @@ -397,7 +397,7 @@ func (m *MMClient) GetPublicLink(filename string) string { if err != nil { return "" } - return res.Data.(string) + return res } func (m *MMClient) GetPublicLinks(filenames []string) []string { @@ -407,7 +407,7 @@ func (m *MMClient) GetPublicLinks(filenames []string) []string { if err != nil { continue } - output = append(output, res.Data.(string)) + output = append(output, res) } return output } @@ -432,15 +432,17 @@ func (m *MMClient) UpdateLastViewed(channelId string) { } func (m *MMClient) UsernamesInChannel(channelId string) []string { - ceiRes, err := m.Client.GetChannelExtraInfo(channelId, 5000, "") + res, err := m.Client.GetMyChannelMembers() if err != nil { m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err) return []string{} } - extra := ceiRes.Data.(*model.ChannelExtra) + members := res.Data.(*model.ChannelMembers) result := []string{} - for _, member := range extra.Members { - result = append(result, member.Username) + for _, channel := range *members { + if channel.ChannelId == channelId { + result = append(result, m.GetUser(channel.UserId).Username) + } } return result } @@ -500,10 +502,10 @@ func (m *MMClient) GetChannels() []*model.Channel { defer m.RUnlock() var channels []*model.Channel // our primary team channels first - channels = append(channels, m.Team.Channels.Channels...) + channels = append(channels, *m.Team.Channels...) for _, t := range m.OtherTeams { if t.Id != m.Team.Id { - channels = append(channels, t.Channels.Channels...) + channels = append(channels, *t.Channels...) } } return channels @@ -515,7 +517,7 @@ func (m *MMClient) GetMoreChannels() []*model.Channel { defer m.RUnlock() var channels []*model.Channel for _, t := range m.OtherTeams { - channels = append(channels, t.MoreChannels.Channels...) + channels = append(channels, *t.MoreChannels...) } return channels } @@ -526,8 +528,8 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { defer m.RUnlock() var channels []*model.Channel for _, t := range m.OtherTeams { - channels = append(channels, t.Channels.Channels...) - channels = append(channels, t.MoreChannels.Channels...) + channels = append(channels, *t.Channels...) + channels = append(channels, *t.MoreChannels...) for _, c := range channels { if c.Id == channelId { return t.Id @@ -540,11 +542,13 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { func (m *MMClient) GetLastViewedAt(channelId string) int64 { m.RLock() defer m.RUnlock() - for _, t := range m.OtherTeams { - if _, ok := t.Channels.Members[channelId]; ok { - return t.Channels.Members[channelId].LastViewedAt + /* + for _, t := range m.OtherTeams { + if _, ok := t.Channels.Members[channelId]; ok { + return t.Channels.Members[channelId].LastViewedAt + } } - } + */ return 0 } @@ -619,7 +623,7 @@ func (m *MMClient) initUser() error { //m.log.Debug("initUser(): loading all team data") for _, v := range initData.Teams { m.Client.SetTeamId(v.Id) - mmusers, _ := m.Client.GetProfiles(v.Id, "") + mmusers, _ := m.Client.GetProfiles(0, 1000, "") t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id} mmchannels, _ := m.Client.GetChannels("") t.Channels = mmchannels.Data.(*model.ChannelList) diff --git a/vendor/github.com/mattermost/platform/einterfaces/cluster.go b/vendor/github.com/mattermost/platform/einterfaces/cluster.go index 921576ad..6b439539 100644 --- a/vendor/github.com/mattermost/platform/einterfaces/cluster.go +++ b/vendor/github.com/mattermost/platform/einterfaces/cluster.go @@ -13,7 +13,6 @@ type ClusterInterface interface { GetClusterInfos() []*model.ClusterInfo RemoveAllSessionsForUserId(userId string) InvalidateCacheForUser(userId string) - InvalidateCacheForChannel(channelId string) Publish(event *model.WebSocketEvent) UpdateStatus(status *model.Status) GetLogs() ([]string, *model.AppError) diff --git a/vendor/github.com/mattermost/platform/einterfaces/mfa.go b/vendor/github.com/mattermost/platform/einterfaces/mfa.go index 25f3ed91..4830d261 100644 --- a/vendor/github.com/mattermost/platform/einterfaces/mfa.go +++ b/vendor/github.com/mattermost/platform/einterfaces/mfa.go @@ -8,7 +8,7 @@ import ( ) type MfaInterface interface { - GenerateQrCode(user *model.User) ([]byte, *model.AppError) + GenerateSecret(user *model.User) (string, []byte, *model.AppError) Activate(user *model.User, token string) *model.AppError Deactivate(userId string) *model.AppError ValidateToken(secret, token string) (bool, *model.AppError) diff --git a/vendor/github.com/mattermost/platform/model/authorization.go b/vendor/github.com/mattermost/platform/model/authorization.go new file mode 100644 index 00000000..75aebf55 --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/authorization.go @@ -0,0 +1,372 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +type Permission struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} + +type Role struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Permissions []string `json:"permissions"` +} + +var PERMISSION_INVITE_USER *Permission +var PERMISSION_ADD_USER_TO_TEAM *Permission +var PERMISSION_USE_SLASH_COMMANDS *Permission +var PERMISSION_MANAGE_SLASH_COMMANDS *Permission +var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission +var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission +var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission +var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission +var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission +var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission +var PERMISSION_MANAGE_ROLES *Permission +var PERMISSION_CREATE_DIRECT_CHANNEL *Permission +var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission +var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission +var PERMISSION_LIST_TEAM_CHANNELS *Permission +var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission +var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission +var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission +var PERMISSION_EDIT_OTHER_USERS *Permission +var PERMISSION_READ_CHANNEL *Permission +var PERMISSION_PERMANENT_DELETE_USER *Permission +var PERMISSION_UPLOAD_FILE *Permission +var PERMISSION_GET_PUBLIC_LINK *Permission +var PERMISSION_MANAGE_WEBHOOKS *Permission +var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission +var PERMISSION_MANAGE_OAUTH *Permission +var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission +var PERMISSION_CREATE_POST *Permission +var PERMISSION_EDIT_POST *Permission +var PERMISSION_EDIT_OTHERS_POSTS *Permission +var PERMISSION_REMOVE_USER_FROM_TEAM *Permission +var PERMISSION_MANAGE_TEAM *Permission +var PERMISSION_IMPORT_TEAM *Permission + +// General permission that encompases all system admin functions +// in the future this could be broken up to allow access to some +// admin functions but not others +var PERMISSION_MANAGE_SYSTEM *Permission + +var ROLE_SYSTEM_USER *Role +var ROLE_SYSTEM_ADMIN *Role + +var ROLE_TEAM_USER *Role +var ROLE_TEAM_ADMIN *Role + +var ROLE_CHANNEL_USER *Role +var ROLE_CHANNEL_ADMIN *Role +var ROLE_CHANNEL_GUEST *Role + +var BuiltInRoles map[string]*Role + +func InitalizePermissions() { + PERMISSION_INVITE_USER = &Permission{ + "invite_user", + "authentication.permissions.team_invite_user.name", + "authentication.permissions.team_invite_user.description", + } + PERMISSION_ADD_USER_TO_TEAM = &Permission{ + "add_user_to_team", + "authentication.permissions.add_user_to_team.name", + "authentication.permissions.add_user_to_team.description", + } + PERMISSION_USE_SLASH_COMMANDS = &Permission{ + "use_slash_commands", + "authentication.permissions.team_use_slash_commands.name", + "authentication.permissions.team_use_slash_commands.description", + } + PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{ + "manage_slash_commands", + "authentication.permissions.manage_slash_commands.name", + "authentication.permissions.manage_slash_commands.description", + } + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{ + "manage_others_slash_commands", + "authentication.permissions.manage_others_slash_commands.name", + "authentication.permissions.manage_others_slash_commands.description", + } + PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{ + "create_public_channel", + "authentication.permissions.create_public_channel.name", + "authentication.permissions.create_public_channel.description", + } + PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{ + "create_private_channel", + "authentication.permissions.create_private_channel.name", + "authentication.permissions.create_private_channel.description", + } + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{ + "manage_public_channel_members", + "authentication.permissions.manage_public_channel_members.name", + "authentication.permissions.manage_public_channel_members.description", + } + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{ + "manage_private_channel_members", + "authentication.permissions.manage_private_channel_members.name", + "authentication.permissions.manage_private_channel_members.description", + } + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{ + "assign_system_admin_role", + "authentication.permissions.assign_system_admin_role.name", + "authentication.permissions.assign_system_admin_role.description", + } + PERMISSION_MANAGE_ROLES = &Permission{ + "manage_roles", + "authentication.permissions.manage_roles.name", + "authentication.permissions.manage_roles.description", + } + PERMISSION_MANAGE_SYSTEM = &Permission{ + "manage_system", + "authentication.permissions.manage_system.name", + "authentication.permissions.manage_system.description", + } + PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{ + "create_direct_channel", + "authentication.permissions.create_direct_channel.name", + "authentication.permissions.create_direct_channel.description", + } + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{ + "manage__publicchannel_properties", + "authentication.permissions.manage_public_channel_properties.name", + "authentication.permissions.manage_public_channel_properties.description", + } + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{ + "manage_private_channel_properties", + "authentication.permissions.manage_private_channel_properties.name", + "authentication.permissions.manage_private_channel_properties.description", + } + PERMISSION_LIST_TEAM_CHANNELS = &Permission{ + "list_team_channels", + "authentication.permissions.list_team_channels.name", + "authentication.permissions.list_team_channels.description", + } + PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{ + "join_public_channels", + "authentication.permissions.join_public_channels.name", + "authentication.permissions.join_public_channels.description", + } + PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{ + "delete_public_channel", + "authentication.permissions.delete_public_channel.name", + "authentication.permissions.delete_public_channel.description", + } + PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{ + "delete_private_channel", + "authentication.permissions.delete_private_channel.name", + "authentication.permissions.delete_private_channel.description", + } + PERMISSION_EDIT_OTHER_USERS = &Permission{ + "edit_other_users", + "authentication.permissions.edit_other_users.name", + "authentication.permissions.edit_other_users.description", + } + PERMISSION_READ_CHANNEL = &Permission{ + "read_channel", + "authentication.permissions.read_channel.name", + "authentication.permissions.read_channel.description", + } + PERMISSION_PERMANENT_DELETE_USER = &Permission{ + "permanent_delete_user", + "authentication.permissions.permanent_delete_user.name", + "authentication.permissions.permanent_delete_user.description", + } + PERMISSION_UPLOAD_FILE = &Permission{ + "upload_file", + "authentication.permissions.upload_file.name", + "authentication.permissions.upload_file.description", + } + PERMISSION_GET_PUBLIC_LINK = &Permission{ + "get_public_link", + "authentication.permissions.get_public_link.name", + "authentication.permissions.get_public_link.description", + } + PERMISSION_MANAGE_WEBHOOKS = &Permission{ + "manage_webhooks", + "authentication.permissions.manage_webhooks.name", + "authentication.permissions.manage_webhooks.description", + } + PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{ + "manage_others_webhooks", + "authentication.permissions.manage_others_webhooks.name", + "authentication.permissions.manage_others_webhooks.description", + } + PERMISSION_MANAGE_OAUTH = &Permission{ + "manage_oauth", + "authentication.permissions.manage_oauth.name", + "authentication.permissions.manage_oauth.description", + } + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ + "manage_sytem_wide_oauth", + "authentication.permissions.manage_sytem_wide_oauth.name", + "authentication.permissions.manage_sytem_wide_oauth.description", + } + PERMISSION_CREATE_POST = &Permission{ + "create_post", + "authentication.permissions.create_post.name", + "authentication.permissions.create_post.description", + } + PERMISSION_EDIT_POST = &Permission{ + "edit_post", + "authentication.permissions.edit_post.name", + "authentication.permissions.edit_post.description", + } + PERMISSION_EDIT_OTHERS_POSTS = &Permission{ + "edit_others_posts", + "authentication.permissions.edit_others_posts.name", + "authentication.permissions.edit_others_posts.description", + } + PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{ + "remove_user_from_team", + "authentication.permissions.remove_user_from_team.name", + "authentication.permissions.remove_user_from_team.description", + } + PERMISSION_MANAGE_TEAM = &Permission{ + "manage_team", + "authentication.permissions.manage_team.name", + "authentication.permissions.manage_team.description", + } + PERMISSION_IMPORT_TEAM = &Permission{ + "import_team", + "authentication.permissions.import_team.name", + "authentication.permissions.import_team.description", + } +} + +func InitalizeRoles() { + InitalizePermissions() + BuiltInRoles = make(map[string]*Role) + + ROLE_CHANNEL_USER = &Role{ + "channel_user", + "authentication.roles.channel_user.name", + "authentication.roles.channel_user.description", + []string{ + PERMISSION_READ_CHANNEL.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + PERMISSION_UPLOAD_FILE.Id, + PERMISSION_GET_PUBLIC_LINK.Id, + PERMISSION_CREATE_POST.Id, + PERMISSION_EDIT_POST.Id, + PERMISSION_USE_SLASH_COMMANDS.Id, + }, + } + BuiltInRoles[ROLE_CHANNEL_USER.Id] = ROLE_CHANNEL_USER + ROLE_CHANNEL_ADMIN = &Role{ + "channel_admin", + "authentication.roles.channel_admin.name", + "authentication.roles.channel_admin.description", + []string{}, + } + BuiltInRoles[ROLE_CHANNEL_ADMIN.Id] = ROLE_CHANNEL_ADMIN + ROLE_CHANNEL_GUEST = &Role{ + "guest", + "authentication.roles.global_guest.name", + "authentication.roles.global_guest.description", + []string{}, + } + BuiltInRoles[ROLE_CHANNEL_GUEST.Id] = ROLE_CHANNEL_GUEST + + ROLE_TEAM_USER = &Role{ + "team_user", + "authentication.roles.team_user.name", + "authentication.roles.team_user.description", + []string{ + PERMISSION_LIST_TEAM_CHANNELS.Id, + PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + }, + } + BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER + ROLE_TEAM_ADMIN = &Role{ + "team_admin", + "authentication.roles.team_admin.name", + "authentication.roles.team_admin.description", + []string{ + PERMISSION_EDIT_OTHERS_POSTS.Id, + PERMISSION_ADD_USER_TO_TEAM.Id, + PERMISSION_REMOVE_USER_FROM_TEAM.Id, + PERMISSION_MANAGE_TEAM.Id, + PERMISSION_IMPORT_TEAM.Id, + PERMISSION_MANAGE_ROLES.Id, + PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + PERMISSION_MANAGE_SLASH_COMMANDS.Id, + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + PERMISSION_MANAGE_WEBHOOKS.Id, + }, + } + BuiltInRoles[ROLE_TEAM_ADMIN.Id] = ROLE_TEAM_ADMIN + + ROLE_SYSTEM_USER = &Role{ + "system_user", + "authentication.roles.global_user.name", + "authentication.roles.global_user.description", + []string{ + PERMISSION_CREATE_DIRECT_CHANNEL.Id, + PERMISSION_PERMANENT_DELETE_USER.Id, + PERMISSION_MANAGE_OAUTH.Id, + }, + } + BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER + ROLE_SYSTEM_ADMIN = &Role{ + "system_admin", + "authentication.roles.global_admin.name", + "authentication.roles.global_admin.description", + // System admins can do anything channel and team admins can do + // plus everything members of teams and channels can do to all teams + // and channels on the system + append( + append( + append( + append( + []string{ + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + PERMISSION_MANAGE_SYSTEM.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + PERMISSION_EDIT_OTHER_USERS.Id, + PERMISSION_MANAGE_OAUTH.Id, + PERMISSION_INVITE_USER.Id, + }, + ROLE_TEAM_USER.Permissions..., + ), + ROLE_CHANNEL_USER.Permissions..., + ), + ROLE_TEAM_ADMIN.Permissions..., + ), + ROLE_CHANNEL_ADMIN.Permissions..., + ), + } + BuiltInRoles[ROLE_SYSTEM_ADMIN.Id] = ROLE_SYSTEM_ADMIN + +} + +func RoleIdsToString(roles []string) string { + output := "" + for _, role := range roles { + output += role + ", " + } + + if output == "" { + return "[]" + } + + return output[:len(output)-1] +} + +func init() { + InitalizeRoles() +} diff --git a/vendor/github.com/mattermost/platform/model/autocomplete.go b/vendor/github.com/mattermost/platform/model/autocomplete.go new file mode 100644 index 00000000..b7449a79 --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/autocomplete.go @@ -0,0 +1,58 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type UserAutocompleteInChannel struct { + InChannel []*User `json:"in_channel"` + OutOfChannel []*User `json:"out_of_channel"` +} + +type UserAutocompleteInTeam struct { + InTeam []*User `json:"in_team"` +} + +func (o *UserAutocompleteInChannel) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel { + decoder := json.NewDecoder(data) + var o UserAutocompleteInChannel + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} + +func (o *UserAutocompleteInTeam) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam { + decoder := json.NewDecoder(data) + var o UserAutocompleteInTeam + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/channel.go b/vendor/github.com/mattermost/platform/model/channel.go index 3da9f4fe..2ad257cc 100644 --- a/vendor/github.com/mattermost/platform/model/channel.go +++ b/vendor/github.com/mattermost/platform/model/channel.go @@ -10,10 +10,14 @@ import ( ) const ( - CHANNEL_OPEN = "O" - CHANNEL_PRIVATE = "P" - CHANNEL_DIRECT = "D" - DEFAULT_CHANNEL = "town-square" + CHANNEL_OPEN = "O" + CHANNEL_PRIVATE = "P" + CHANNEL_DIRECT = "D" + DEFAULT_CHANNEL = "town-square" + CHANNEL_DISPLAY_NAME_MAX_RUNES = 64 + CHANNEL_NAME_MAX_LENGTH = 64 + CHANNEL_HEADER_MAX_RUNES = 1024 + CHANNEL_PURPOSE_MAX_RUNES = 250 ) type Channel struct { @@ -57,8 +61,8 @@ func (o *Channel) Etag() string { return Etag(o.Id, o.UpdateAt) } -func (o *Channel) ExtraEtag(memberLimit int) string { - return Etag(o.Id, o.ExtraUpdateAt, memberLimit) +func (o *Channel) StatsEtag() string { + return Etag(o.Id, o.ExtraUpdateAt) } func (o *Channel) IsValid() *AppError { @@ -75,11 +79,11 @@ func (o *Channel) IsValid() *AppError { return NewLocAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id) } - if utf8.RuneCountInString(o.DisplayName) > 64 { + if utf8.RuneCountInString(o.DisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES { return NewLocAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id) } - if len(o.Name) > 64 { + if len(o.Name) > CHANNEL_NAME_MAX_LENGTH { return NewLocAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "id="+o.Id) } @@ -91,11 +95,11 @@ func (o *Channel) IsValid() *AppError { return NewLocAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id) } - if utf8.RuneCountInString(o.Header) > 1024 { + if utf8.RuneCountInString(o.Header) > CHANNEL_HEADER_MAX_RUNES { return NewLocAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id) } - if utf8.RuneCountInString(o.Purpose) > 128 { + if utf8.RuneCountInString(o.Purpose) > CHANNEL_PURPOSE_MAX_RUNES { return NewLocAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id) } diff --git a/vendor/github.com/mattermost/platform/model/channel_extra.go b/vendor/github.com/mattermost/platform/model/channel_extra.go deleted file mode 100644 index 55da588a..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_extra.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type ExtraMember struct { - Id string `json:"id"` - Nickname string `json:"nickname"` - Email string `json:"email"` - Roles string `json:"roles"` - Username string `json:"username"` -} - -func (o *ExtraMember) Sanitize(options map[string]bool) { - if len(options) == 0 || !options["email"] { - o.Email = "" - } -} - -type ChannelExtra struct { - Id string `json:"id"` - Members []ExtraMember `json:"members"` - MemberCount int64 `json:"member_count"` -} - -func (o *ChannelExtra) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ChannelExtraFromJson(data io.Reader) *ChannelExtra { - decoder := json.NewDecoder(data) - var o ChannelExtra - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/channel_list.go b/vendor/github.com/mattermost/platform/model/channel_list.go index 49ba384a..7a46de45 100644 --- a/vendor/github.com/mattermost/platform/model/channel_list.go +++ b/vendor/github.com/mattermost/platform/model/channel_list.go @@ -8,15 +8,11 @@ import ( "io" ) -type ChannelList struct { - Channels []*Channel `json:"channels"` - Members map[string]*ChannelMember `json:"members"` -} +type ChannelList []*Channel func (o *ChannelList) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" + if b, err := json.Marshal(o); err != nil { + return "[]" } else { return string(b) } @@ -28,7 +24,7 @@ func (o *ChannelList) Etag() string { var t int64 = 0 var delta int64 = 0 - for _, v := range o.Channels { + for _, v := range *o { if v.LastPostAt > t { t = v.LastPostAt id = v.Id @@ -39,30 +35,9 @@ func (o *ChannelList) Etag() string { id = v.Id } - member := o.Members[v.Id] - - if member != nil { - max := v.LastPostAt - if v.UpdateAt > max { - max = v.UpdateAt - } - - delta += max - member.LastViewedAt - - if member.LastViewedAt > t { - t = member.LastViewedAt - id = v.Id - } - - if member.LastUpdateAt > t { - t = member.LastUpdateAt - id = v.Id - } - - } } - return Etag(id, t, delta, len(o.Channels)) + return Etag(id, t, delta, len(*o)) } func ChannelListFromJson(data io.Reader) *ChannelList { diff --git a/vendor/github.com/mattermost/platform/model/channel_member.go b/vendor/github.com/mattermost/platform/model/channel_member.go index 66e20da6..4180bb8e 100644 --- a/vendor/github.com/mattermost/platform/model/channel_member.go +++ b/vendor/github.com/mattermost/platform/model/channel_member.go @@ -10,7 +10,6 @@ import ( ) const ( - CHANNEL_ROLE_ADMIN = "admin" CHANNEL_NOTIFY_DEFAULT = "default" CHANNEL_NOTIFY_ALL = "all" CHANNEL_NOTIFY_MENTION = "mention" @@ -30,6 +29,27 @@ type ChannelMember struct { LastUpdateAt int64 `json:"last_update_at"` } +type ChannelMembers []ChannelMember + +func (o *ChannelMembers) ToJson() string { + if b, err := json.Marshal(o); err != nil { + return "[]" + } else { + return string(b) + } +} + +func ChannelMembersFromJson(data io.Reader) *ChannelMembers { + decoder := json.NewDecoder(data) + var o ChannelMembers + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} + func (o *ChannelMember) ToJson() string { b, err := json.Marshal(o) if err != nil { @@ -60,12 +80,6 @@ func (o *ChannelMember) IsValid() *AppError { return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "") } - for _, role := range strings.Split(o.Roles, " ") { - if !(role == "" || role == CHANNEL_ROLE_ADMIN) { - return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.role.app_error", nil, "role="+role) - } - } - notifyLevel := o.NotifyProps["desktop"] if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) { return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error", @@ -89,6 +103,10 @@ func (o *ChannelMember) PreUpdate() { o.LastUpdateAt = GetMillis() } +func (o *ChannelMember) GetRoles() []string { + return strings.Fields(o.Roles) +} + func IsChannelNotifyLevelValid(notifyLevel string) bool { return notifyLevel == CHANNEL_NOTIFY_DEFAULT || notifyLevel == CHANNEL_NOTIFY_ALL || diff --git a/vendor/github.com/mattermost/platform/model/channel_stats.go b/vendor/github.com/mattermost/platform/model/channel_stats.go new file mode 100644 index 00000000..079769eb --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/channel_stats.go @@ -0,0 +1,34 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ChannelStats struct { + ChannelId string `json:"channel_id"` + MemberCount int64 `json:"member_count"` +} + +func (o *ChannelStats) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func ChannelStatsFromJson(data io.Reader) *ChannelStats { + decoder := json.NewDecoder(data) + var o ChannelStats + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/client.go b/vendor/github.com/mattermost/platform/model/client.go index e54f6134..8a361c17 100644 --- a/vendor/github.com/mattermost/platform/model/client.go +++ b/vendor/github.com/mattermost/platform/model/client.go @@ -108,6 +108,10 @@ func (c *Client) GetChannelRoute(channelId string) string { return fmt.Sprintf("/teams/%v/channels/%v", c.GetTeamId(), channelId) } +func (c *Client) GetUserRequiredRoute(userId string) string { + return fmt.Sprintf("/users/%v", userId) +} + func (c *Client) GetChannelNameRoute(channelName string) string { return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) } @@ -120,9 +124,14 @@ func (c *Client) GetGeneralRoute() string { return "/general" } +func (c *Client) GetFileRoute(fileId string) string { + return fmt.Sprintf("/files/%v", fileId) +} + func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) { rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data)) rq.Header.Set("Content-Type", contentType) + rq.Close = true if rp, err := c.HttpClient.Do(rq); err != nil { return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) @@ -136,6 +145,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) { rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data)) + rq.Close = true if len(c.AuthToken) > 0 { rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) @@ -153,6 +163,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) { rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data)) + rq.Close = true if len(etag) > 0 { rq.Header.Set(HEADER_ETAG_CLIENT, etag) @@ -500,10 +511,9 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) { } } -// GetProfilesForDirectMessageList returns a map of users for a team that can be direct -// messaged, using user id as the key. Must be authenticated. -func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { +// GetProfiles returns a map of users using user id as the key. Must be authenticated. +func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil { return nil, err } else { defer closeBody(r) @@ -512,10 +522,10 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr } } -// GetProfiles returns a map of users for a team using user id as the key. Must +// GetProfilesInTeam returns a map of users for a team using user id as the key. Must // be authenticated. -func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { +func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil { return nil, err } else { defer closeBody(r) @@ -524,10 +534,10 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { } } -// GetDirectProfiles gets a map of users that are currently shown in the sidebar, -// using user id as the key. Must be authenticated. -func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { +// GetProfilesInChannel returns a map of users for a channel using user id as the key. Must +// be authenticated. +func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil { return nil, err } else { defer closeBody(r) @@ -536,6 +546,70 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { } } +// GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must +// be authenticated. +func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil + } +} + +// GetProfilesByIds returns a map of users based on the user ids provided. Must +// be authenticated. +func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) { + if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil + } +} + +// SearchUsers returns a list of users that have a username matching or similar to the search term. Must +// be authenticated. +func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) { + if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil + } +} + +// AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel", +// specifies users in the channel. The second list "out_of_channel" specifies users outside of the +// channel. Term, the string to search against, is required, channel id is also required. Must be authenticated. +func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) { + url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term)) + if r, err := c.DoApiGet(url, "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil + } +} + +// AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies +// the users in the team that match the provided term, matching against username, full name and +// nickname. Must be authenticated. +func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) { + url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) + if r, err := c.DoApiGet(url, "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil + } +} + // LoginById authenticates a user by user id and password. func (c *Client) LoginById(id string, password string) (*Result, *AppError) { m := make(map[string]string) @@ -622,15 +696,16 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { } } -// GenerateMfaQrCode returns a QR code imagem containing the secret, to be scanned -// by a multi-factor authentication mobile application. Must be authenticated. -func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { +// GenerateMfaSecret returns a QR code image containing the secret, to be scanned +// by a multi-factor authentication mobile application. It also returns the secret +// for manual entry. Must be authenticated. +func (c *Client) GenerateMfaSecret() (*Result, *AppError) { + if r, err := c.DoApiGet("/users/generate_mfa_secret", "", ""); err != nil { return nil, err } else { defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), r.Body}, nil + r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil } } @@ -934,6 +1009,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) { func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) { var rq *http.Request rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil) + rq.Close = true if len(c.AuthToken) > 0 { rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) @@ -1047,13 +1123,13 @@ func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) } } -func (c *Client) GetChannels(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { +func (c *Client) GetMyChannelMembers() (*Result, *AppError) { + if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/members", "", ""); 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 + r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil } } @@ -1087,6 +1163,16 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { } } +func (c *Client) GetChannels(etag string) (*Result, *AppError) { + if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", 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 + } +} + func (c *Client) JoinChannel(id string) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil { return nil, err @@ -1166,13 +1252,23 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap } } -func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil { +func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) { + if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil { return nil, err } else { defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil + r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil + } +} + +func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) { + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil } } @@ -1285,13 +1381,39 @@ func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *A return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) } -func (c *Client) UploadPostAttachment(data []byte, contentType string) (*Result, *AppError) { - return c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", data, contentType) +func (c *Client) UploadPostAttachment(data []byte, channelId string, filename string) (*FileUploadResponse, *AppError) { + c.clearExtraProperties() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("files", filename); err != nil { + return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error()) + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error()) + } + + if part, err := writer.CreateFormField("channel_id"); err != nil { + return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error()) + } else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil { + return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error()) + } + + if err := writer.Close(); err != nil { + return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error()) + } + + if result, err := c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", body.Bytes(), writer.FormDataContentType()); err != nil { + return nil, err + } else { + return result.Data.(*FileUploadResponse), nil + } } func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) { rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) rq.Header.Set("Content-Type", contentType) + rq.Close = true if len(c.AuthToken) > 0 { rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) @@ -1308,55 +1430,51 @@ func (c *Client) uploadFile(url string, data []byte, contentType string) (*Resul } } -func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) { - var rq *http.Request - if isFullUrl { - rq, _ = http.NewRequest("GET", url, nil) +func (c *Client) GetFile(fileId string) (io.ReadCloser, *AppError) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get", "", ""); err != nil { + return nil, err } else { - rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get"+url, nil) - } - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) - } else if rp.StatusCode >= 300 { - return nil, AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - return &Result{rp.Header.Get(HEADER_REQUEST_ID), - rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil + c.fillInExtraProperties(r) + return r.Body, nil } } -func (c *Client) GetFileInfo(url string) (*Result, *AppError) { - var rq *http.Request - rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get_info"+url, nil) - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) - } else if rp.StatusCode >= 300 { - return nil, AppErrorFromJson(rp.Body) +func (c *Client) GetFileThumbnail(fileId string) (io.ReadCloser, *AppError) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_thumbnail", "", ""); err != nil { + return nil, err } else { - defer closeBody(rp) - return &Result{rp.Header.Get(HEADER_REQUEST_ID), - rp.Header.Get(HEADER_ETAG_SERVER), FileInfoFromJson(rp.Body)}, nil + c.fillInExtraProperties(r) + return r.Body, nil } } -func (c *Client) GetPublicLink(filename string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/files/get_public_link", MapToJson(map[string]string{"filename": filename})); err != nil { +func (c *Client) GetFilePreview(fileId string) (io.ReadCloser, *AppError) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_preview", "", ""); err != nil { return nil, err } else { defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), StringFromJson(r.Body)}, nil + c.fillInExtraProperties(r) + return r.Body, nil + } +} + +func (c *Client) GetFileInfo(fileId string) (*FileInfo, *AppError) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_info", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + c.fillInExtraProperties(r) + return FileInfoFromJson(r.Body), nil + } +} + +func (c *Client) GetPublicLink(fileId string) (string, *AppError) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_public_link", "", ""); err != nil { + return "", err + } else { + defer closeBody(r) + c.fillInExtraProperties(r) + return StringFromJson(r.Body), nil } } @@ -1370,8 +1488,25 @@ func (c *Client) UpdateUser(user *User) (*Result, *AppError) { } } -func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/update_roles", MapToJson(data)); err != nil { +func (c *Client) UpdateUserRoles(userId string, roles string) (*Result, *AppError) { + data := make(map[string]string) + data["new_roles"] = roles + + if r, err := c.DoApiPost(c.GetUserRequiredRoute(userId)+"/update_roles", 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) UpdateTeamRoles(userId string, roles string) (*Result, *AppError) { + data := make(map[string]string) + data["new_roles"] = roles + data["user_id"] = userId + + if r, err := c.DoApiPost(c.GetTeamRoute()+"/update_member_roles", MapToJson(data)); err != nil { return nil, err } else { defer closeBody(r) @@ -1479,6 +1614,18 @@ func (c *Client) GetStatuses() (*Result, *AppError) { } } +// GetStatusesByIds returns a map of string statuses using user id as the key, +// based on the provided user ids +func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) { + if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); 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 + } +} + // 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. @@ -1504,8 +1651,46 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { } } -func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil { +// GetTeamMembers will return a page of team member objects as an array paged based on the +// team id, offset and limit provided. Must be authenticated. +func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil + } +} + +// GetTeamMember will return a team member object based on the team id and user id provided. +// Must be authenticated. +func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil + } +} + +// GetTeamStats will return a team stats object containing the number of users on the team +// based on the team id provided. Must be authenticated. +func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) { + if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil + } +} + +// GetTeamMembersByIds will return team member objects as an array based on the +// team id and a list of user ids provided. Must be authenticated. +func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) { + if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil { return nil, err } else { defer closeBody(r) @@ -1820,6 +2005,7 @@ func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoj rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body) rq.Header.Set("Content-Type", writer.FormDataContentType()) + rq.Close = true if len(c.AuthToken) > 0 { rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) @@ -1862,6 +2048,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro url := c.ApiUrl + "/admin/add_certificate" rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) rq.Header.Set("Content-Type", contentType) + rq.Close = true if len(c.AuthToken) > 0 { rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) @@ -1898,3 +2085,28 @@ func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, return StringInterfaceFromJson(r.Body), nil } } + +// GetWebrtcToken if Successful returns a map with a valid token, stun server and turn server with credentials to use with +// the Mattermost WebRTC service, otherwise returns an AppError. Must be authenticated user. +func (c *Client) GetWebrtcToken() (map[string]string, *AppError) { + if r, err := c.DoApiPost("/webrtc/token", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return MapFromJson(r.Body), nil + } +} + +// GetFileInfosForPost returns a list of FileInfo objects for a given post id, if successful. +// Otherwise, it returns an error. +func (c *Client) GetFileInfosForPost(channelId string, postId string, etag string) ([]*FileInfo, *AppError) { + c.clearExtraProperties() + + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get_file_infos", postId), "", etag); err != nil { + return nil, err + } else { + defer closeBody(r) + c.fillInExtraProperties(r) + return FileInfosFromJson(r.Body), nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/compliance_post.go b/vendor/github.com/mattermost/platform/model/compliance_post.go index ce26a366..027e534b 100644 --- a/vendor/github.com/mattermost/platform/model/compliance_post.go +++ b/vendor/github.com/mattermost/platform/model/compliance_post.go @@ -34,7 +34,7 @@ type CompliancePost struct { PostType string PostProps string PostHashtags string - PostFilenames string + PostFileIds string } func CompliancePostHeader() []string { @@ -60,7 +60,7 @@ func CompliancePostHeader() []string { "PostType", "PostProps", "PostHashtags", - "PostFilenames", + "PostFileIds", } } @@ -99,6 +99,6 @@ func (me *CompliancePost) Row() []string { me.PostType, me.PostProps, me.PostHashtags, - me.PostFilenames, + me.PostFileIds, } } diff --git a/vendor/github.com/mattermost/platform/model/config.go b/vendor/github.com/mattermost/platform/model/config.go index 3ad1c4c6..f2ff788d 100644 --- a/vendor/github.com/mattermost/platform/model/config.go +++ b/vendor/github.com/mattermost/platform/model/config.go @@ -57,6 +57,14 @@ const ( type ServiceSettings struct { SiteURL *string ListenAddress string + ConnectionSecurity *string + TLSCertFile *string + TLSKeyFile *string + UseLetsEncrypt *bool + LetsEncryptCertificateCacheFile *string + Forward80To443 *bool + ReadTimeout *int + WriteTimeout *int MaximumLoginAttempts int SegmentDeveloperKey string GoogleDeveloperKey string @@ -130,26 +138,24 @@ type PasswordSettings struct { } type FileSettings struct { - MaxFileSize *int64 - DriverName string - Directory string - EnablePublicLink bool - PublicLinkSalt *string - ThumbnailWidth int - ThumbnailHeight int - PreviewWidth int - PreviewHeight int - ProfileWidth int - ProfileHeight int - InitialFont string - AmazonS3AccessKeyId string - AmazonS3SecretAccessKey string - AmazonS3Bucket string - AmazonS3Region string - AmazonS3Endpoint string - AmazonS3BucketEndpoint string - AmazonS3LocationConstraint *bool - AmazonS3LowercaseBucket *bool + MaxFileSize *int64 + DriverName string + Directory string + EnablePublicLink bool + PublicLinkSalt *string + ThumbnailWidth int + ThumbnailHeight int + PreviewWidth int + PreviewHeight int + ProfileWidth int + ProfileHeight int + InitialFont string + AmazonS3AccessKeyId string + AmazonS3SecretAccessKey string + AmazonS3Bucket string + AmazonS3Region string + AmazonS3Endpoint string + AmazonS3SSL *bool } type EmailSettings struct { @@ -177,11 +183,12 @@ type EmailSettings struct { } type RateLimitSettings struct { - EnableRateLimiter bool - PerSec int - MemoryStoreSize int - VaryByRemoteAddr bool - VaryByHeader string + Enable *bool + PerSec int + MaxBurst *int + MemoryStoreSize int + VaryByRemoteAddr bool + VaryByHeader string } type PrivacySettings struct { @@ -205,7 +212,6 @@ type TeamSettings struct { EnableUserCreation bool EnableOpenServer *bool RestrictCreationToDomains string - RestrictTeamNames *bool EnableCustomBrand *bool CustomBrandText *string CustomDescriptionText *string @@ -214,6 +220,7 @@ type TeamSettings struct { RestrictPublicChannelManagement *string RestrictPrivateChannelManagement *string UserStatusAwayTimeout *int64 + MaxChannelsPerTeam *int64 } type LdapSettings struct { @@ -292,6 +299,17 @@ type NativeAppSettings struct { IosAppDownloadLink *string } +type WebrtcSettings struct { + Enable *bool + GatewayWebsocketUrl *string + GatewayAdminUrl *string + GatewayAdminSecret *string + StunURI *string + TurnURI *string + TurnUsername *string + TurnSharedKey *string +} + type Config struct { ServiceSettings ServiceSettings TeamSettings TeamSettings @@ -312,6 +330,7 @@ type Config struct { SamlSettings SamlSettings NativeAppSettings NativeAppSettings ClusterSettings ClusterSettings + WebrtcSettings WebrtcSettings } func (o *Config) ToJson() string { @@ -353,26 +372,27 @@ func (o *Config) SetDefaults() { o.SqlSettings.AtRestEncryptKey = NewRandomString(32) } + if o.FileSettings.AmazonS3Endpoint == "" { + // Defaults to "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 { + o.FileSettings.AmazonS3SSL = new(bool) + *o.FileSettings.AmazonS3SSL = true // Secure by default. + } if o.FileSettings.MaxFileSize == nil { o.FileSettings.MaxFileSize = new(int64) *o.FileSettings.MaxFileSize = 52428800 // 50 MB } - if len(*o.FileSettings.PublicLinkSalt) == 0 { o.FileSettings.PublicLinkSalt = new(string) *o.FileSettings.PublicLinkSalt = NewRandomString(32) } - if o.FileSettings.AmazonS3LocationConstraint == nil { - o.FileSettings.AmazonS3LocationConstraint = new(bool) - *o.FileSettings.AmazonS3LocationConstraint = false - } - - if o.FileSettings.AmazonS3LowercaseBucket == nil { - o.FileSettings.AmazonS3LowercaseBucket = new(bool) - *o.FileSettings.AmazonS3LowercaseBucket = false - } - if len(o.EmailSettings.InviteSalt) == 0 { o.EmailSettings.InviteSalt = NewRandomString(32) } @@ -431,11 +451,6 @@ func (o *Config) SetDefaults() { *o.PasswordSettings.Symbol = false } - if o.TeamSettings.RestrictTeamNames == nil { - o.TeamSettings.RestrictTeamNames = new(bool) - *o.TeamSettings.RestrictTeamNames = true - } - if o.TeamSettings.EnableCustomBrand == nil { o.TeamSettings.EnableCustomBrand = new(bool) *o.TeamSettings.EnableCustomBrand = false @@ -481,6 +496,11 @@ func (o *Config) SetDefaults() { *o.TeamSettings.UserStatusAwayTimeout = 300 } + if o.TeamSettings.MaxChannelsPerTeam == nil { + o.TeamSettings.MaxChannelsPerTeam = new(int64) + *o.TeamSettings.MaxChannelsPerTeam = 2000 + } + if o.EmailSettings.EnableSignInWithEmail == nil { o.EmailSettings.EnableSignInWithEmail = new(bool) @@ -881,6 +901,58 @@ func (o *Config) SetDefaults() { o.NativeAppSettings.IosAppDownloadLink = new(string) *o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/" } + + if o.RateLimitSettings.Enable == nil { + o.RateLimitSettings.Enable = new(bool) + *o.RateLimitSettings.Enable = false + } + + if o.RateLimitSettings.MaxBurst == nil { + o.RateLimitSettings.MaxBurst = new(int) + *o.RateLimitSettings.MaxBurst = 100 + } + + if o.ServiceSettings.ConnectionSecurity == nil { + o.ServiceSettings.ConnectionSecurity = new(string) + *o.ServiceSettings.ConnectionSecurity = "" + } + + if o.ServiceSettings.TLSKeyFile == nil { + o.ServiceSettings.TLSKeyFile = new(string) + *o.ServiceSettings.TLSKeyFile = "" + } + + if o.ServiceSettings.TLSCertFile == nil { + o.ServiceSettings.TLSCertFile = new(string) + *o.ServiceSettings.TLSCertFile = "" + } + + if o.ServiceSettings.UseLetsEncrypt == nil { + o.ServiceSettings.UseLetsEncrypt = new(bool) + *o.ServiceSettings.UseLetsEncrypt = false + } + + if o.ServiceSettings.LetsEncryptCertificateCacheFile == nil { + o.ServiceSettings.LetsEncryptCertificateCacheFile = new(string) + *o.ServiceSettings.LetsEncryptCertificateCacheFile = "./config/letsencrypt.cache" + } + + if o.ServiceSettings.ReadTimeout == nil { + o.ServiceSettings.ReadTimeout = new(int) + *o.ServiceSettings.ReadTimeout = 300 + } + + if o.ServiceSettings.WriteTimeout == nil { + o.ServiceSettings.WriteTimeout = new(int) + *o.ServiceSettings.WriteTimeout = 300 + } + + if o.ServiceSettings.Forward80To443 == nil { + o.ServiceSettings.Forward80To443 = new(bool) + *o.ServiceSettings.Forward80To443 = false + } + + o.defaultWebrtcSettings() } func (o *Config) IsValid() *AppError { @@ -911,6 +983,10 @@ func (o *Config) IsValid() *AppError { return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "") } + if *o.TeamSettings.MaxChannelsPerTeam <= 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "") + } + if !(*o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_ANY || *o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_TEAM) { return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "") } @@ -1083,6 +1159,26 @@ func (o *Config) IsValid() *AppError { return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "") } + if *o.RateLimitSettings.MaxBurst <= 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.max_burst.app_error", nil, "") + } + + if err := o.isValidWebrtcSettings(); err != nil { + return err + } + + if !(*o.ServiceSettings.ConnectionSecurity == CONN_SECURITY_NONE || *o.ServiceSettings.ConnectionSecurity == CONN_SECURITY_TLS) { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "") + } + + if *o.ServiceSettings.ReadTimeout <= 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "") + } + + if *o.ServiceSettings.WriteTimeout <= 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "") + } + return nil } @@ -1121,3 +1217,71 @@ func (o *Config) Sanitize() { o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING } } + +func (o *Config) defaultWebrtcSettings() { + if o.WebrtcSettings.Enable == nil { + o.WebrtcSettings.Enable = new(bool) + *o.WebrtcSettings.Enable = false + } + + if o.WebrtcSettings.GatewayWebsocketUrl == nil { + o.WebrtcSettings.GatewayWebsocketUrl = new(string) + *o.WebrtcSettings.GatewayWebsocketUrl = "" + } + + if o.WebrtcSettings.GatewayAdminUrl == nil { + o.WebrtcSettings.GatewayAdminUrl = new(string) + *o.WebrtcSettings.GatewayAdminUrl = "" + } + + if o.WebrtcSettings.GatewayAdminSecret == nil { + o.WebrtcSettings.GatewayAdminSecret = new(string) + *o.WebrtcSettings.GatewayAdminSecret = "" + } + + if o.WebrtcSettings.StunURI == nil { + o.WebrtcSettings.StunURI = new(string) + *o.WebrtcSettings.StunURI = "" + } + + if o.WebrtcSettings.TurnURI == nil { + o.WebrtcSettings.TurnURI = new(string) + *o.WebrtcSettings.TurnURI = "" + } + + if o.WebrtcSettings.TurnUsername == nil { + o.WebrtcSettings.TurnUsername = new(string) + *o.WebrtcSettings.TurnUsername = "" + } + + if o.WebrtcSettings.TurnSharedKey == nil { + o.WebrtcSettings.TurnSharedKey = new(string) + *o.WebrtcSettings.TurnSharedKey = "" + } +} + +func (o *Config) isValidWebrtcSettings() *AppError { + if *o.WebrtcSettings.Enable { + if len(*o.WebrtcSettings.GatewayWebsocketUrl) == 0 || !IsValidWebsocketUrl(*o.WebrtcSettings.GatewayWebsocketUrl) { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_ws_url.app_error", nil, "") + } else if len(*o.WebrtcSettings.GatewayAdminUrl) == 0 || !IsValidHttpUrl(*o.WebrtcSettings.GatewayAdminUrl) { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_url.app_error", nil, "") + } else if len(*o.WebrtcSettings.GatewayAdminSecret) == 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_secret.app_error", nil, "") + } else if len(*o.WebrtcSettings.StunURI) != 0 && !IsValidTurnOrStunServer(*o.WebrtcSettings.StunURI) { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_stun_uri.app_error", nil, "") + } else if len(*o.WebrtcSettings.TurnURI) != 0 { + if !IsValidTurnOrStunServer(*o.WebrtcSettings.TurnURI) { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_uri.app_error", nil, "") + } + if len(*o.WebrtcSettings.TurnUsername) == 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_username.app_error", nil, "") + } else if len(*o.WebrtcSettings.TurnSharedKey) == 0 { + return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_shared_key.app_error", nil, "") + } + + } + } + + return nil +} diff --git a/vendor/github.com/mattermost/platform/model/file.go b/vendor/github.com/mattermost/platform/model/file.go index fa98a3b3..c218c424 100644 --- a/vendor/github.com/mattermost/platform/model/file.go +++ b/vendor/github.com/mattermost/platform/model/file.go @@ -14,8 +14,8 @@ var ( ) type FileUploadResponse struct { - Filenames []string `json:"filenames"` - ClientIds []string `json:"client_ids"` + FileInfos []*FileInfo `json:"file_infos"` + ClientIds []string `json:"client_ids"` } func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse { diff --git a/vendor/github.com/mattermost/platform/model/file_info.go b/vendor/github.com/mattermost/platform/model/file_info.go index f785042b..687473d4 100644 --- a/vendor/github.com/mattermost/platform/model/file_info.go +++ b/vendor/github.com/mattermost/platform/model/file_info.go @@ -6,54 +6,31 @@ package model import ( "bytes" "encoding/json" + "image" "image/gif" "io" "mime" "path/filepath" + "strings" ) type FileInfo struct { - Filename string `json:"filename"` - Size int `json:"size"` + Id string `json:"id"` + CreatorId string `json:"user_id"` + PostId string `json:"post_id,omitempty"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + Path string `json:"-"` // not sent back to the client + ThumbnailPath string `json:"-"` // not sent back to the client + PreviewPath string `json:"-"` // not sent back to the client + Name string `json:"name"` Extension string `json:"extension"` + Size int64 `json:"size"` MimeType string `json:"mime_type"` - HasPreviewImage bool `json:"has_preview_image"` -} - -func GetInfoForBytes(filename string, data []byte) (*FileInfo, *AppError) { - size := len(data) - - var mimeType string - extension := filepath.Ext(filename) - isImage := IsFileExtImage(extension) - if isImage { - mimeType = GetImageMimeType(extension) - } else { - mimeType = mime.TypeByExtension(extension) - } - - if extension != "" && extension[0] == '.' { - // the client expects a file extension without the leading period - extension = extension[1:] - } - - hasPreviewImage := isImage - if mimeType == "image/gif" { - // just show the gif itself instead of a preview image for animated gifs - if gifImage, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { - return nil, NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "filename="+filename) - } else { - hasPreviewImage = len(gifImage.Image) == 1 - } - } - - return &FileInfo{ - Filename: filename, - Size: size, - Extension: extension, - MimeType: mimeType, - HasPreviewImage: hasPreviewImage, - }, nil + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + HasPreviewImage bool `json:"has_preview_image,omitempty"` } func (info *FileInfo) ToJson() string { @@ -75,3 +52,123 @@ func FileInfoFromJson(data io.Reader) *FileInfo { return &info } } + +func FileInfosToJson(infos []*FileInfo) string { + b, err := json.Marshal(infos) + if err != nil { + return "" + } else { + return string(b) + } +} + +func FileInfosFromJson(data io.Reader) []*FileInfo { + decoder := json.NewDecoder(data) + + var infos []*FileInfo + if err := decoder.Decode(&infos); err != nil { + return nil + } else { + return infos + } +} + +func (o *FileInfo) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + o.UpdateAt = o.CreateAt + } +} + +func (o *FileInfo) IsValid() *AppError { + if len(o.Id) != 26 { + return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "") + } + + if len(o.CreatorId) != 26 { + return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id) + } + + if len(o.PostId) != 0 && len(o.PostId) != 26 { + return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+o.Id) + } + + if o.CreateAt == 0 { + return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+o.Id) + } + + if o.UpdateAt == 0 { + return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+o.Id) + } + + if o.Path == "" { + return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+o.Id) + } + + return nil +} + +func (o *FileInfo) IsImage() bool { + return strings.HasPrefix(o.MimeType, "image") +} + +func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) { + info := &FileInfo{ + Name: name, + Size: int64(len(data)), + } + var err *AppError + + extension := strings.ToLower(filepath.Ext(name)) + info.MimeType = mime.TypeByExtension(extension) + + if extension != "" && extension[0] == '.' { + // The client expects a file extension without the leading period + info.Extension = extension[1:] + } else { + info.Extension = extension + } + + if info.IsImage() { + // Only set the width and height if it's actually an image that we can understand + if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil { + info.Width = config.Width + info.Height = config.Height + + if info.MimeType == "image/gif" { + // Just show the gif itself instead of a preview image for animated gifs + if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { + // Still return the rest of the info even though it doesn't appear to be an actual gif + info.HasPreviewImage = true + err = NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name) + } else { + info.HasPreviewImage = len(gifConfig.Image) == 1 + } + } else { + info.HasPreviewImage = true + } + } + } + + return info, err +} + +func GetEtagForFileInfos(infos []*FileInfo) string { + if len(infos) == 0 { + return Etag() + } + + var maxUpdateAt int64 + + for _, info := range infos { + if info.UpdateAt > maxUpdateAt { + maxUpdateAt = info.UpdateAt + } + } + + return Etag(infos[0].PostId, maxUpdateAt) +} diff --git a/vendor/github.com/mattermost/platform/model/initial_load.go b/vendor/github.com/mattermost/platform/model/initial_load.go index d7587e6d..afb0a276 100644 --- a/vendor/github.com/mattermost/platform/model/initial_load.go +++ b/vendor/github.com/mattermost/platform/model/initial_load.go @@ -9,14 +9,13 @@ import ( ) type InitialLoad struct { - User *User `json:"user"` - TeamMembers []*TeamMember `json:"team_members"` - Teams []*Team `json:"teams"` - DirectProfiles map[string]*User `json:"direct_profiles"` - Preferences Preferences `json:"preferences"` - ClientCfg map[string]string `json:"client_cfg"` - LicenseCfg map[string]string `json:"license_cfg"` - NoAccounts bool `json:"no_accounts"` + User *User `json:"user"` + TeamMembers []*TeamMember `json:"team_members"` + Teams []*Team `json:"teams"` + Preferences Preferences `json:"preferences"` + ClientCfg map[string]string `json:"client_cfg"` + LicenseCfg map[string]string `json:"license_cfg"` + NoAccounts bool `json:"no_accounts"` } func (me *InitialLoad) ToJson() string { diff --git a/vendor/github.com/mattermost/platform/model/license.go b/vendor/github.com/mattermost/platform/model/license.go index 1fce1eeb..8d8d0068 100644 --- a/vendor/github.com/mattermost/platform/model/license.go +++ b/vendor/github.com/mattermost/platform/model/license.go @@ -43,7 +43,8 @@ type Features struct { MHPNS *bool `json:"mhpns"` SAML *bool `json:"saml"` PasswordRequirements *bool `json:"password_requirements"` - FutureFeatures *bool `json:"future_features"` + // after we enabled more features for webrtc we'll need to control them with this + FutureFeatures *bool `json:"future_features"` } func (f *Features) ToMap() map[string]interface{} { diff --git a/vendor/github.com/mattermost/platform/model/post.go b/vendor/github.com/mattermost/platform/model/post.go index 33caeb9e..da14b650 100644 --- a/vendor/github.com/mattermost/platform/model/post.go +++ b/vendor/github.com/mattermost/platform/model/post.go @@ -35,7 +35,8 @@ type Post struct { Type string `json:"type"` Props StringInterface `json:"props"` Hashtags string `json:"hashtags"` - Filenames StringArray `json:"filenames"` + Filenames StringArray `json:"filenames,omitempty"` // Deprecated, do not use this field any more + FileIds StringArray `json:"file_ids,omitempty"` PendingPostId string `json:"pending_post_id" db:"-"` } @@ -118,6 +119,10 @@ func (o *Post) IsValid() *AppError { return NewLocAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id) } + if utf8.RuneCountInString(ArrayToJson(o.FileIds)) > 150 { + return NewLocAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id) + } + if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > 8000 { return NewLocAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id) } @@ -145,15 +150,16 @@ func (o *Post) PreSave() { if o.Filenames == nil { o.Filenames = []string{} } + + if o.FileIds == nil { + o.FileIds = []string{} + } } func (o *Post) MakeNonNil() { if o.Props == nil { o.Props = make(map[string]interface{}) } - if o.Filenames == nil { - o.Filenames = []string{} - } } func (o *Post) AddProp(key string, value interface{}) { diff --git a/vendor/github.com/mattermost/platform/model/session.go b/vendor/github.com/mattermost/platform/model/session.go index e8b04fbe..a6a753e7 100644 --- a/vendor/github.com/mattermost/platform/model/session.go +++ b/vendor/github.com/mattermost/platform/model/session.go @@ -11,7 +11,7 @@ import ( const ( SESSION_COOKIE_TOKEN = "MMAUTHTOKEN" - SESSION_CACHE_SIZE = 10000 + SESSION_CACHE_SIZE = 25000 SESSION_PROP_PLATFORM = "platform" SESSION_PROP_OS = "os" SESSION_PROP_BROWSER = "browser" @@ -115,6 +115,10 @@ func (me *Session) IsMobileApp() bool { (strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_ANDROID+":")) } +func (me *Session) GetUserRoles() []string { + return strings.Fields(me.Roles) +} + func SessionsToJson(o []*Session) string { if b, err := json.Marshal(o); err != nil { return "[]" diff --git a/vendor/github.com/mattermost/platform/model/status.go b/vendor/github.com/mattermost/platform/model/status.go index f4ad8e77..32486642 100644 --- a/vendor/github.com/mattermost/platform/model/status.go +++ b/vendor/github.com/mattermost/platform/model/status.go @@ -12,8 +12,9 @@ const ( STATUS_OFFLINE = "offline" STATUS_AWAY = "away" STATUS_ONLINE = "online" - STATUS_CACHE_SIZE = 10000 - STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds + STATUS_CACHE_SIZE = 25000 + STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds + STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes ) type Status struct { diff --git a/vendor/github.com/mattermost/platform/model/team.go b/vendor/github.com/mattermost/platform/model/team.go index dccc0219..d54a809f 100644 --- a/vendor/github.com/mattermost/platform/model/team.go +++ b/vendor/github.com/mattermost/platform/model/team.go @@ -100,7 +100,7 @@ func (o *Team) Etag() string { return Etag(o.Id, o.UpdateAt) } -func (o *Team) IsValid(restrictTeamNames bool) *AppError { +func (o *Team) IsValid() *AppError { if len(o.Id) != 26 { return NewLocAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "") @@ -130,7 +130,7 @@ func (o *Team) IsValid(restrictTeamNames bool) *AppError { return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id) } - if restrictTeamNames && IsReservedTeamName(o.Name) { + if IsReservedTeamName(o.Name) { return NewLocAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id) } @@ -188,7 +188,7 @@ func IsValidTeamName(s string) bool { return false } - if len(s) <= 3 { + if len(s) <= 1 { return false } diff --git a/vendor/github.com/mattermost/platform/model/team_member.go b/vendor/github.com/mattermost/platform/model/team_member.go index 7d932dec..a040e916 100644 --- a/vendor/github.com/mattermost/platform/model/team_member.go +++ b/vendor/github.com/mattermost/platform/model/team_member.go @@ -9,10 +9,6 @@ import ( "strings" ) -const ( - ROLE_TEAM_ADMIN = "admin" -) - type TeamMember struct { TeamId string `json:"team_id"` UserId string `json:"user_id"` @@ -59,48 +55,6 @@ func TeamMembersFromJson(data io.Reader) []*TeamMember { } } -func IsValidTeamRoles(teamRoles string) bool { - - roles := strings.Split(teamRoles, " ") - - for _, r := range roles { - if !isValidTeamRole(r) { - return false - } - } - - return true -} - -func isValidTeamRole(role string) bool { - if role == "" { - return true - } - - if role == ROLE_TEAM_ADMIN { - return true - } - - return false -} - -func IsInTeamRole(teamRoles string, inRole string) bool { - roles := strings.Split(teamRoles, " ") - - for _, r := range roles { - if r == inRole { - return true - } - - } - - return false -} - -func (o *TeamMember) IsTeamAdmin() bool { - return IsInTeamRole(o.Roles, ROLE_TEAM_ADMIN) -} - func (o *TeamMember) IsValid() *AppError { if len(o.TeamId) != 26 { @@ -111,11 +65,12 @@ func (o *TeamMember) IsValid() *AppError { return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "") } - for _, role := range strings.Split(o.Roles, " ") { - if !(role == "" || role == ROLE_TEAM_ADMIN) { - return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.role.app_error", nil, "role="+role) - } - } - return nil } + +func (o *TeamMember) PreUpdate() { +} + +func (o *TeamMember) GetRoles() []string { + return strings.Fields(o.Roles) +} diff --git a/vendor/github.com/mattermost/platform/model/team_stats.go b/vendor/github.com/mattermost/platform/model/team_stats.go new file mode 100644 index 00000000..9042e76d --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/team_stats.go @@ -0,0 +1,35 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type TeamStats struct { + TeamId string `json:"team_id"` + TotalMemberCount int64 `json:"total_member_count"` + ActiveMemberCount int64 `json:"active_member_count"` +} + +func (o *TeamStats) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func TeamStatsFromJson(data io.Reader) *TeamStats { + decoder := json.NewDecoder(data) + var o TeamStats + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/user.go b/vendor/github.com/mattermost/platform/model/user.go index 680bc48c..330d26d8 100644 --- a/vendor/github.com/mattermost/platform/model/user.go +++ b/vendor/github.com/mattermost/platform/model/user.go @@ -15,7 +15,6 @@ import ( ) const ( - ROLE_SYSTEM_ADMIN = "system_admin" USER_NOTIFY_ALL = "all" USER_NOTIFY_MENTION = "mention" USER_NOTIFY_NONE = "none" @@ -233,14 +232,15 @@ func (u *User) Sanitize(options map[string]bool) { if len(options) != 0 && !options["passwordupdate"] { u.LastPasswordUpdate = 0 } + if len(options) != 0 && !options["authservice"] { + u.AuthService = "" + } } func (u *User) ClearNonProfileFields() { u.Password = "" u.AuthData = new(string) *u.AuthData = "" - u.AuthService = "" - u.MfaActive = false u.MfaSecret = "" u.EmailVerified = false u.AllowMarketing = false @@ -319,9 +319,17 @@ func (u *User) GetDisplayNameForPreference(nameFormat string) string { return displayName } +func (u *User) GetRoles() []string { + return strings.Fields(u.Roles) +} + +func (u *User) GetRawRoles() string { + return u.Roles +} + func IsValidUserRoles(userRoles string) bool { - roles := strings.Split(userRoles, " ") + roles := strings.Fields(userRoles) for _, r := range roles { if !isValidRole(r) { @@ -329,19 +337,17 @@ func IsValidUserRoles(userRoles string) bool { } } + // Exclude just the system_admin role explicitly to prevent mistakes + if len(roles) == 1 && roles[0] == "system_admin" { + return false + } + return true } -func isValidRole(role string) bool { - if role == "" { - return true - } - - if role == ROLE_SYSTEM_ADMIN { - return true - } - - return false +func isValidRole(roleId string) bool { + _, ok := BuiltInRoles[roleId] + return ok } // Make sure you acually want to use this function. In context.go there are functions to check permissions @@ -411,6 +417,26 @@ func UserMapFromJson(data io.Reader) map[string]*User { } } +func UserListToJson(u []*User) string { + b, err := json.Marshal(u) + if err != nil { + return "" + } else { + return string(b) + } +} + +func UserListFromJson(data io.Reader) []*User { + decoder := json.NewDecoder(data) + var users []*User + err := decoder.Decode(&users) + if err == nil { + return users + } else { + return nil + } +} + // HashPassword generates a hash using the bcrypt.GenerateFromPassword func HashPassword(password string) string { hash, err := bcrypt.GenerateFromPassword([]byte(password), 10) diff --git a/vendor/github.com/mattermost/platform/model/user_search.go b/vendor/github.com/mattermost/platform/model/user_search.go new file mode 100644 index 00000000..4bbd2bd7 --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/user_search.go @@ -0,0 +1,39 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type UserSearch struct { + Term string `json:"term"` + TeamId string `json:"team_id"` + InChannelId string `json:"in_channel_id"` + NotInChannelId string `json:"not_in_channel_id"` + AllowInactive bool `json:"allow_inactive"` +} + +// ToJson convert a User to a json string +func (u *UserSearch) ToJson() string { + b, err := json.Marshal(u) + if err != nil { + return "" + } else { + return string(b) + } +} + +// UserSearchFromJson will decode the input and return a User +func UserSearchFromJson(data io.Reader) *UserSearch { + decoder := json.NewDecoder(data) + var us UserSearch + err := decoder.Decode(&us) + if err == nil { + return &us + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/utils.go b/vendor/github.com/mattermost/platform/model/utils.go index a4a4208c..457b64c0 100644 --- a/vendor/github.com/mattermost/platform/model/utils.go +++ b/vendor/github.com/mattermost/platform/model/utils.go @@ -10,6 +10,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "net/mail" "net/url" "regexp" @@ -74,13 +75,21 @@ func (er *AppError) ToJson() string { // AppErrorFromJson will decode the input and return an AppError func AppErrorFromJson(data io.Reader) *AppError { - decoder := json.NewDecoder(data) + str := "" + bytes, rerr := ioutil.ReadAll(data) + if rerr != nil { + str = rerr.Error() + } else { + str = string(bytes) + } + + decoder := json.NewDecoder(strings.NewReader(str)) var er AppError err := decoder.Decode(&er) if err == nil { return &er } else { - return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error()) + return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str) } } @@ -166,6 +175,23 @@ func ArrayFromJson(data io.Reader) []string { } } +func ArrayFromInterface(data interface{}) []string { + stringArray := []string{} + + dataArray, ok := data.([]interface{}) + if !ok { + return stringArray + } + + for _, v := range dataArray { + if str, ok := v.(string); ok { + stringArray = append(stringArray, str) + } + } + + return stringArray +} + func StringInterfaceToJson(objmap map[string]interface{}) string { if b, err := json.Marshal(objmap); err != nil { return "" @@ -227,58 +253,15 @@ func IsValidEmail(email string) bool { } var reservedName = []string{ - "www", - "web", + "signup", + "login", "admin", - "support", - "notify", - "test", - "demo", - "mail", - "team", "channel", - "internal", - "localhost", - "dockerhost", - "stag", "post", - "cluster", "api", "oauth", } -var wwwStart = regexp.MustCompile(`^www`) -var betaStart = regexp.MustCompile(`^beta`) -var ciStart = regexp.MustCompile(`^ci`) - -func GetSubDomain(s string) (string, string) { - s = strings.Replace(s, "http://", "", 1) - s = strings.Replace(s, "https://", "", 1) - - match := wwwStart.MatchString(s) - if match { - return "", "" - } - - match = betaStart.MatchString(s) - if match { - return "", "" - } - - match = ciStart.MatchString(s) - if match { - return "", "" - } - - parts := strings.Split(s, ".") - - if len(parts) != 3 { - return "", "" - } - - return parts[0], parts[1] -} - func IsValidChannelIdentifier(s string) bool { if !IsValidAlphaNum(s, true) { @@ -413,6 +396,18 @@ func IsValidHttpsUrl(rawUrl string) bool { return true } +func IsValidTurnOrStunServer(rawUri string) bool { + if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { + return false + } + + if _, err := url.ParseRequestURI(rawUri); err != nil { + return false + } + + return true +} + func IsSafeLink(link *string) bool { if link != nil { if IsValidHttpUrl(*link) { @@ -426,3 +421,15 @@ func IsSafeLink(link *string) bool { return true } + +func IsValidWebsocketUrl(rawUrl string) bool { + if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { + return false + } + + if _, err := url.ParseRequestURI(rawUrl); err != nil { + return false + } + + return true +} diff --git a/vendor/github.com/mattermost/platform/model/version.go b/vendor/github.com/mattermost/platform/model/version.go index 6a0072cd..9d9d8fc1 100644 --- a/vendor/github.com/mattermost/platform/model/version.go +++ b/vendor/github.com/mattermost/platform/model/version.go @@ -13,6 +13,7 @@ import ( // It should be maitained in chronological order with most current // release at the front of the list. var versions = []string{ + "3.5.0", "3.4.0", "3.3.0", "3.2.0", diff --git a/vendor/github.com/mattermost/platform/model/webrtc.go b/vendor/github.com/mattermost/platform/model/webrtc.go new file mode 100644 index 00000000..e746d62a --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/webrtc.go @@ -0,0 +1,21 @@ +package model + +import ( + "encoding/json" + "io" +) + +type GatewayResponse struct { + Status string `json:"janus"` +} + +func GatewayResponseFromJson(data io.Reader) *GatewayResponse { + decoder := json.NewDecoder(data) + var o GatewayResponse + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/websocket_client.go b/vendor/github.com/mattermost/platform/model/websocket_client.go index a048bd85..453ae49b 100644 --- a/vendor/github.com/mattermost/platform/model/websocket_client.go +++ b/vendor/github.com/mattermost/platform/model/websocket_client.go @@ -6,7 +6,6 @@ package model import ( "encoding/json" "github.com/gorilla/websocket" - "net/http" ) type WebSocketClient struct { @@ -17,19 +16,18 @@ type WebSocketClient struct { Sequence int64 // The ever-incrementing sequence attached to each WebSocket action EventChannel chan *WebSocketEvent ResponseChannel chan *WebSocketResponse + ListenError *AppError } // NewWebSocketClient constructs a new WebSocket client with convienence // methods for talking to the server. func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { - header := http.Header{} - header.Set(HEADER_AUTH, "BEARER "+authToken) - conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", header) + conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", nil) if err != nil { return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) } - return &WebSocketClient{ + client := &WebSocketClient{ url, url + API_URL_SUFFIX, conn, @@ -37,19 +35,26 @@ func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { 1, make(chan *WebSocketEvent, 100), make(chan *WebSocketResponse, 100), - }, nil + nil, + } + + client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) + + return client, nil } func (wsc *WebSocketClient) Connect() *AppError { - header := http.Header{} - header.Set(HEADER_AUTH, "BEARER "+wsc.AuthToken) - var err error - wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", header) + wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", nil) if err != nil { return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) } + wsc.EventChannel = make(chan *WebSocketEvent, 100) + wsc.ResponseChannel = make(chan *WebSocketResponse, 100) + + wsc.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": wsc.AuthToken}) + return nil } @@ -59,10 +64,20 @@ func (wsc *WebSocketClient) Close() { func (wsc *WebSocketClient) Listen() { go func() { + defer func() { + wsc.Conn.Close() + close(wsc.EventChannel) + close(wsc.ResponseChannel) + }() + for { var rawMsg json.RawMessage var err error if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { + if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { + wsc.ListenError = NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) + } + return } @@ -77,6 +92,7 @@ func (wsc *WebSocketClient) Listen() { wsc.ResponseChannel <- &response continue } + } }() } @@ -107,3 +123,12 @@ func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { func (wsc *WebSocketClient) GetStatuses() { wsc.SendMessage("get_statuses", nil) } + +// GetStatusesByIds will fetch certain user statuses based on ids and return +// a map of string statuses using user id as the key +func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) { + data := map[string]interface{}{ + "user_ids": userIds, + } + wsc.SendMessage("get_statuses_by_ids", data) +} diff --git a/vendor/github.com/mattermost/platform/model/websocket_message.go b/vendor/github.com/mattermost/platform/model/websocket_message.go index 18e070af..5eb02642 100644 --- a/vendor/github.com/mattermost/platform/model/websocket_message.go +++ b/vendor/github.com/mattermost/platform/model/websocket_message.go @@ -25,33 +25,57 @@ const ( WEBSOCKET_EVENT_EPHEMERAL_MESSAGE = "ephemeral_message" WEBSOCKET_EVENT_STATUS_CHANGE = "status_change" WEBSOCKET_EVENT_HELLO = "hello" + WEBSOCKET_EVENT_WEBRTC = "webrtc" + WEBSOCKET_AUTHENTICATION_CHALLENGE = "authentication_challenge" ) type WebSocketMessage interface { ToJson() string IsValid() bool + DoPreComputeJson() + GetPreComputeJson() []byte +} + +type WebsocketBroadcast struct { + OmitUsers map[string]bool `json:"omit_users"` // broadcast is omitted for users listed here + UserId string `json:"user_id"` // broadcast only occurs for this user + ChannelId string `json:"channel_id"` // broadcast only occurs for users in this channel + TeamId string `json:"team_id"` // broadcast only occurs for users in this team } type WebSocketEvent struct { - TeamId string `json:"team_id"` - ChannelId string `json:"channel_id"` - UserId string `json:"user_id"` - Event string `json:"event"` - Data map[string]interface{} `json:"data"` + Event string `json:"event"` + Data map[string]interface{} `json:"data"` + Broadcast *WebsocketBroadcast `json:"broadcast"` + PreComputeJson []byte `json:"-"` } func (m *WebSocketEvent) Add(key string, value interface{}) { m.Data[key] = value } -func NewWebSocketEvent(teamId string, channelId string, userId string, event string) *WebSocketEvent { - return &WebSocketEvent{TeamId: teamId, ChannelId: channelId, UserId: userId, Event: event, Data: make(map[string]interface{})} +func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { + return &WebSocketEvent{Event: event, Data: make(map[string]interface{}), + Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}} } func (o *WebSocketEvent) IsValid() bool { return o.Event != "" } +func (o *WebSocketEvent) DoPreComputeJson() { + b, err := json.Marshal(o) + if err != nil { + o.PreComputeJson = []byte("") + } else { + o.PreComputeJson = b + } +} + +func (o *WebSocketEvent) GetPreComputeJson() []byte { + return o.PreComputeJson +} + func (o *WebSocketEvent) ToJson() string { b, err := json.Marshal(o) if err != nil { @@ -73,10 +97,11 @@ func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { } type WebSocketResponse struct { - Status string `json:"status"` - SeqReply int64 `json:"seq_reply,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Error *AppError `json:"error,omitempty"` + Status string `json:"status"` + SeqReply int64 `json:"seq_reply,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` + Error *AppError `json:"error,omitempty"` + PreComputeJson []byte `json:"-"` } func (m *WebSocketResponse) Add(key string, value interface{}) { @@ -104,6 +129,19 @@ func (o *WebSocketResponse) ToJson() string { } } +func (o *WebSocketResponse) DoPreComputeJson() { + b, err := json.Marshal(o) + if err != nil { + o.PreComputeJson = []byte("") + } else { + o.PreComputeJson = b + } +} + +func (o *WebSocketResponse) GetPreComputeJson() []byte { + return o.PreComputeJson +} + func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { decoder := json.NewDecoder(data) var o WebSocketResponse diff --git a/vendor/manifest b/vendor/manifest index e530affa..fd4a0969 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -87,8 +87,8 @@ "importpath": "github.com/mattermost/platform/einterfaces", "repository": "https://github.com/mattermost/platform", "vcs": "git", - "revision": "57f25fa59c71821cc38fd220b133aa6a40815e12", - "branch": "release-3.4", + "revision": "b55ec6148caa93d54b660afe55408c643d217108", + "branch": "release-3.5", "path": "/einterfaces", "notests": true }, @@ -96,8 +96,8 @@ "importpath": "github.com/mattermost/platform/model", "repository": "https://github.com/mattermost/platform", "vcs": "git", - "revision": "57f25fa59c71821cc38fd220b133aa6a40815e12", - "branch": "release-3.4", + "revision": "b55ec6148caa93d54b660afe55408c643d217108", + "branch": "release-3.5", "path": "/model", "notests": true },