From 1d5cd1d7c479c382c9cddaf02f1e59bf55971f12 Mon Sep 17 00:00:00 2001
From: Wim <wim@42.be>
Date: Sat, 12 Nov 2016 22:00:53 +0100
Subject: [PATCH] Sync with mattermost 3.5.0

---
 bridge/mattermost/mattermost.go               |   2 +-
 matterclient/matterclient.go                  |  48 +--
 .../platform/einterfaces/cluster.go           |   1 -
 .../mattermost/platform/einterfaces/mfa.go    |   2 +-
 .../platform/model/authorization.go           | 372 ++++++++++++++++++
 .../mattermost/platform/model/autocomplete.go |  58 +++
 .../mattermost/platform/model/channel.go      |  24 +-
 .../platform/model/channel_extra.go           |  49 ---
 .../mattermost/platform/model/channel_list.go |  35 +-
 .../platform/model/channel_member.go          |  32 +-
 .../platform/model/channel_stats.go           |  34 ++
 .../mattermost/platform/model/client.go       | 344 ++++++++++++----
 .../platform/model/compliance_post.go         |   6 +-
 .../mattermost/platform/model/config.go       | 248 ++++++++++--
 .../mattermost/platform/model/file.go         |   4 +-
 .../mattermost/platform/model/file_info.go    | 175 ++++++--
 .../mattermost/platform/model/initial_load.go |  15 +-
 .../mattermost/platform/model/license.go      |   3 +-
 .../mattermost/platform/model/post.go         |  14 +-
 .../mattermost/platform/model/session.go      |   6 +-
 .../mattermost/platform/model/status.go       |   5 +-
 .../mattermost/platform/model/team.go         |   6 +-
 .../mattermost/platform/model/team_member.go  |  59 +--
 .../mattermost/platform/model/team_stats.go   |  35 ++
 .../mattermost/platform/model/user.go         |  54 ++-
 .../mattermost/platform/model/user_search.go  |  39 ++
 .../mattermost/platform/model/utils.go        | 101 ++---
 .../mattermost/platform/model/version.go      |   1 +
 .../mattermost/platform/model/webrtc.go       |  21 +
 .../platform/model/websocket_client.go        |  45 ++-
 .../platform/model/websocket_message.go       |  60 ++-
 vendor/manifest                               |   8 +-
 32 files changed, 1476 insertions(+), 430 deletions(-)
 create mode 100644 vendor/github.com/mattermost/platform/model/authorization.go
 create mode 100644 vendor/github.com/mattermost/platform/model/autocomplete.go
 delete mode 100644 vendor/github.com/mattermost/platform/model/channel_extra.go
 create mode 100644 vendor/github.com/mattermost/platform/model/channel_stats.go
 create mode 100644 vendor/github.com/mattermost/platform/model/team_stats.go
 create mode 100644 vendor/github.com/mattermost/platform/model/user_search.go
 create mode 100644 vendor/github.com/mattermost/platform/model/webrtc.go

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 "[<NO ROLES>]"
+	}
+
+	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
 		},