From 24defcb970838133eb82f4fbbc516630fb81cae6 Mon Sep 17 00:00:00 2001 From: Wim Date: Mon, 15 Aug 2016 18:47:31 +0200 Subject: [PATCH] Sync with mattermost 3.3.0 --- .../platform/einterfaces/account_migration.go | 20 ++ .../platform/einterfaces/cluster.go | 32 ++++ .../mattermost/platform/einterfaces/ldap.go | 2 + .../mattermost/platform/model/access.go | 25 ++- .../mattermost/platform/model/authorize.go | 5 + .../mattermost/platform/model/channel.go | 3 - .../mattermost/platform/model/client.go | 174 +++++++++++++++++- .../mattermost/platform/model/cluster_info.go | 66 +++++++ .../mattermost/platform/model/config.go | 130 +++++++++++-- .../mattermost/platform/model/job.go | 6 + .../mattermost/platform/model/license.go | 20 +- .../mattermost/platform/model/message.go | 61 ------ .../mattermost/platform/model/oauth.go | 40 +++- .../platform/model/outgoing_webhook.go | 20 ++ .../mattermost/platform/model/post.go | 3 - .../mattermost/platform/model/preference.go | 48 ++++- .../mattermost/platform/model/session.go | 6 +- .../mattermost/platform/model/status.go | 42 +++++ .../mattermost/platform/model/team.go | 3 - .../mattermost/platform/model/user.go | 49 +---- .../mattermost/platform/model/utils.go | 12 +- .../mattermost/platform/model/version.go | 1 + .../platform/model/websocket_client.go | 109 +++++++++++ .../platform/model/websocket_message.go | 114 ++++++++++++ .../platform/model/websocket_request.go | 43 +++++ vendor/manifest | 10 +- 26 files changed, 879 insertions(+), 165 deletions(-) create mode 100644 vendor/github.com/mattermost/platform/einterfaces/account_migration.go create mode 100644 vendor/github.com/mattermost/platform/einterfaces/cluster.go create mode 100644 vendor/github.com/mattermost/platform/model/cluster_info.go delete mode 100644 vendor/github.com/mattermost/platform/model/message.go create mode 100644 vendor/github.com/mattermost/platform/model/status.go create mode 100644 vendor/github.com/mattermost/platform/model/websocket_client.go create mode 100644 vendor/github.com/mattermost/platform/model/websocket_message.go create mode 100644 vendor/github.com/mattermost/platform/model/websocket_request.go diff --git a/vendor/github.com/mattermost/platform/einterfaces/account_migration.go b/vendor/github.com/mattermost/platform/einterfaces/account_migration.go new file mode 100644 index 00000000..4824de6d --- /dev/null +++ b/vendor/github.com/mattermost/platform/einterfaces/account_migration.go @@ -0,0 +1,20 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package einterfaces + +import "github.com/mattermost/platform/model" + +type AccountMigrationInterface interface { + MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError +} + +var theAccountMigrationInterface AccountMigrationInterface + +func RegisterAccountMigrationInterface(newInterface AccountMigrationInterface) { + theAccountMigrationInterface = newInterface +} + +func GetAccountMigrationInterface() AccountMigrationInterface { + return theAccountMigrationInterface +} diff --git a/vendor/github.com/mattermost/platform/einterfaces/cluster.go b/vendor/github.com/mattermost/platform/einterfaces/cluster.go new file mode 100644 index 00000000..921576ad --- /dev/null +++ b/vendor/github.com/mattermost/platform/einterfaces/cluster.go @@ -0,0 +1,32 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package einterfaces + +import ( + "github.com/mattermost/platform/model" +) + +type ClusterInterface interface { + StartInterNodeCommunication() + StopInterNodeCommunication() + GetClusterInfos() []*model.ClusterInfo + RemoveAllSessionsForUserId(userId string) + InvalidateCacheForUser(userId string) + InvalidateCacheForChannel(channelId string) + Publish(event *model.WebSocketEvent) + UpdateStatus(status *model.Status) + GetLogs() ([]string, *model.AppError) + GetClusterId() string + ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError +} + +var theClusterInterface ClusterInterface + +func RegisterClusterInterface(newInterface ClusterInterface) { + theClusterInterface = newInterface +} + +func GetClusterInterface() ClusterInterface { + return theClusterInterface +} diff --git a/vendor/github.com/mattermost/platform/einterfaces/ldap.go b/vendor/github.com/mattermost/platform/einterfaces/ldap.go index 4f1b5611..fb14a8f0 100644 --- a/vendor/github.com/mattermost/platform/einterfaces/ldap.go +++ b/vendor/github.com/mattermost/platform/einterfaces/ldap.go @@ -15,6 +15,8 @@ type LdapInterface interface { ValidateFilter(filter string) *model.AppError Syncronize() *model.AppError StartLdapSyncJob() + SyncNow() + GetAllLdapUsers() ([]*model.User, *model.AppError) } var theLdapInterface LdapInterface diff --git a/vendor/github.com/mattermost/platform/model/access.go b/vendor/github.com/mattermost/platform/model/access.go index 877b3c4f..85417fce 100644 --- a/vendor/github.com/mattermost/platform/model/access.go +++ b/vendor/github.com/mattermost/platform/model/access.go @@ -15,10 +15,12 @@ const ( ) type AccessData struct { - AuthCode string `json:"auth_code"` + ClientId string `json:"client_id"` + UserId string `json:"user_id"` Token string `json:"token"` RefreshToken string `json:"refresh_token"` RedirectUri string `json:"redirect_uri"` + ExpiresAt int64 `json:"expires_at"` } type AccessResponse struct { @@ -33,8 +35,12 @@ type AccessResponse struct { // correctly. func (ad *AccessData) IsValid() *AppError { - if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 { - return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "") + if len(ad.ClientId) == 0 || len(ad.ClientId) > 26 { + return NewLocAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "") + } + + if len(ad.UserId) == 0 || len(ad.UserId) > 26 { + return NewLocAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "") } if len(ad.Token) != 26 { @@ -52,6 +58,19 @@ func (ad *AccessData) IsValid() *AppError { return nil } +func (me *AccessData) IsExpired() bool { + + if me.ExpiresAt <= 0 { + return false + } + + if GetMillis() > me.ExpiresAt { + return true + } + + return false +} + func (ad *AccessData) ToJson() string { b, err := json.Marshal(ad) if err != nil { diff --git a/vendor/github.com/mattermost/platform/model/authorize.go b/vendor/github.com/mattermost/platform/model/authorize.go index e0d665ba..2b4017e9 100644 --- a/vendor/github.com/mattermost/platform/model/authorize.go +++ b/vendor/github.com/mattermost/platform/model/authorize.go @@ -11,6 +11,7 @@ import ( const ( AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes AUTHCODE_RESPONSE_TYPE = "code" + DEFAULT_SCOPE = "user" ) type AuthData struct { @@ -71,6 +72,10 @@ func (ad *AuthData) PreSave() { if ad.CreateAt == 0 { ad.CreateAt = GetMillis() } + + if len(ad.Scope) == 0 { + ad.Scope = DEFAULT_SCOPE + } } func (ad *AuthData) ToJson() string { diff --git a/vendor/github.com/mattermost/platform/model/channel.go b/vendor/github.com/mattermost/platform/model/channel.go index e7002e3c..3da9f4fe 100644 --- a/vendor/github.com/mattermost/platform/model/channel.go +++ b/vendor/github.com/mattermost/platform/model/channel.go @@ -124,9 +124,6 @@ func (o *Channel) ExtraUpdated() { o.ExtraUpdateAt = GetMillis() } -func (o *Channel) PreExport() { -} - func GetDMNameFromIds(userId1, userId2 string) string { if userId1 > userId2 { return userId2 + "__" + userId1 diff --git a/vendor/github.com/mattermost/platform/model/client.go b/vendor/github.com/mattermost/platform/model/client.go index 2f1e846c..2d154e49 100644 --- a/vendor/github.com/mattermost/platform/model/client.go +++ b/vendor/github.com/mattermost/platform/model/client.go @@ -20,6 +20,7 @@ import ( const ( HEADER_REQUEST_ID = "X-Request-ID" HEADER_VERSION_ID = "X-Version-ID" + HEADER_CLUSTER_ID = "X-Cluster-ID" HEADER_ETAG_SERVER = "ETag" HEADER_ETAG_CLIENT = "If-None-Match" HEADER_FORWARDED = "X-Forwarded-For" @@ -32,6 +33,7 @@ const ( HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" STATUS = "status" STATUS_OK = "OK" + STATUS_FAIL = "FAIL" API_URL_SUFFIX_V1 = "/api/v1" API_URL_SUFFIX_V3 = "/api/v3" @@ -276,6 +278,9 @@ func (c *Client) GetPing() (map[string]string, *AppError) { // Team Routes Section +// SignupTeam sends an email with a team sign-up link to the provided address if email +// verification is enabled, otherwise it returns a map with a "follow_link" entry +// containing the team sign-up link. func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { m := make(map[string]string) m["email"] = email @@ -289,6 +294,8 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro } } +// CreateTeamFromSignup creates a team based on the provided TeamSignup struct. On success +// it returns the TeamSignup struct. func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) { if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { return nil, err @@ -299,6 +306,8 @@ func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppErro } } +// CreateTeam creates a team based on the provided Team struct. On success it returns +// the Team struct with the Id, CreateAt and other server-decided fields populated. func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { return nil, err @@ -309,6 +318,7 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { } } +// GetAllTeams returns a map of all teams using team ids as the key. func (c *Client) GetAllTeams() (*Result, *AppError) { if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { return nil, err @@ -319,6 +329,8 @@ func (c *Client) GetAllTeams() (*Result, *AppError) { } } +// GetAllTeamListings returns a map of all teams that are available to join +// using team ids as the key. Must be authenticated. func (c *Client) GetAllTeamListings() (*Result, *AppError) { if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { return nil, err @@ -329,6 +341,8 @@ func (c *Client) GetAllTeamListings() (*Result, *AppError) { } } +// FindTeamByName returns the strings "true" or "false" depending on if a team +// with the provided name was found. func (c *Client) FindTeamByName(name string) (*Result, *AppError) { m := make(map[string]string) m["name"] = name @@ -365,6 +379,8 @@ func (c *Client) AddUserToTeam(teamId string, userId string) (*Result, *AppError } } +// AddUserToTeamFromInvite adds a user to a team based off data provided in an invite link. +// Either hash and dataToHash are required or inviteId is required. func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Result, *AppError) { data := make(map[string]string) data["hash"] = hash @@ -409,6 +425,9 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { } } +// UpdateTeam updates a team based on the changes in the provided team struct. On success +// it returns a sanitized version of the updated team. Must be authenticated as a team admin +// for that team or a system admin. func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { return nil, err @@ -419,6 +438,9 @@ func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { } } +// User Routes Section + +// CreateUser creates a user in the system based on the provided user struct. func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { return nil, err @@ -429,6 +451,8 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { } } +// CreateUserWithInvite creates a user based on the provided user struct. Either the hash and +// data strings or the inviteId is required from the invite. func (c *Client) CreateUserWithInvite(user *User, hash string, data string, inviteId string) (*Result, *AppError) { url := "/users/create?d=" + url.QueryEscape(data) + "&h=" + url.QueryEscape(hash) + "&iid=" + url.QueryEscape(inviteId) @@ -452,6 +476,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re } } +// GetUser returns a user based on a provided user id string. Must be authenticated. func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { return nil, err @@ -462,6 +487,7 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { } } +// GetMe returns the current user. func (c *Client) GetMe(etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/me", "", etag); err != nil { return nil, err @@ -472,6 +498,8 @@ 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 { return nil, err @@ -482,6 +510,8 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr } } +// GetProfiles 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 { return nil, err @@ -492,6 +522,8 @@ 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 { return nil, err @@ -502,6 +534,7 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { } } +// LoginById authenticates a user by user id and password. func (c *Client) LoginById(id string, password string) (*Result, *AppError) { m := make(map[string]string) m["id"] = id @@ -509,6 +542,8 @@ func (c *Client) LoginById(id string, password string) (*Result, *AppError) { return c.login(m) } +// Login authenticates a user by login id, which can be username, email or some sort +// of SSO identifier based on configuration, and a password. func (c *Client) Login(loginId string, password string) (*Result, *AppError) { m := make(map[string]string) m["login_id"] = loginId @@ -516,6 +551,7 @@ func (c *Client) Login(loginId string, password string) (*Result, *AppError) { return c.login(m) } +// LoginByLdap authenticates a user by LDAP id and password. func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { m := make(map[string]string) m["login_id"] = loginId @@ -524,6 +560,9 @@ func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppErro return c.login(m) } +// LoginWithDevice authenticates a user by login id (username, email or some sort +// of SSO identifier based on configuration), password and attaches a device id to +// the session. func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { m := make(map[string]string) m["login_id"] = loginId @@ -550,6 +589,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) { } } +// Logout terminates the current user's session. func (c *Client) Logout() (*Result, *AppError) { if r, err := c.DoApiPost("/users/logout", ""); err != nil { return nil, err @@ -564,6 +604,9 @@ func (c *Client) Logout() (*Result, *AppError) { } } +// CheckMfa returns a map with key "mfa_required" with the string value "true" or "false", +// indicating whether MFA is required to log the user in, based on a provided login id +// (username, email or some sort of SSO identifier based on configuration). func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { m := make(map[string]string) m["login_id"] = loginId @@ -577,6 +620,8 @@ 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 { return nil, err @@ -587,6 +632,9 @@ func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { } } +// UpdateMfa activates multi-factor authenticates for the current user if activate +// is true and a valid token is provided. If activate is false, then token is not +// required and multi-factor authentication is disabled for the current user. func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { m := make(map[string]interface{}) m["activate"] = activate @@ -761,6 +809,15 @@ func (c *Client) GetLogs() (*Result, *AppError) { } } +func (c *Client) GetClusterStatus() ([]*ClusterInfo, *AppError) { + if r, err := c.DoApiGet("/admin/cluster_status", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return ClusterInfosFromJson(r.Body), nil + } +} + func (c *Client) GetAllAudits() (*Result, *AppError) { if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { return nil, err @@ -1181,6 +1238,18 @@ func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) } } +// GetFlaggedPosts will return a post list of posts that have been flagged by the user. +// The page is set by the integer parameters offset and limit. +func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) { + if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/flagged/%v/%v", 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), PostListFromJson(r.Body)}, nil + } +} + func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) } @@ -1368,8 +1437,9 @@ func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppEr } } -func (c *Client) GetStatuses(data []string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil { +// GetStatuses returns a map of string statuses using user id as the key +func (c *Client) GetStatuses() (*Result, *AppError) { + if r, err := c.DoApiGet("/users/status", "", ""); err != nil { return nil, err } else { defer closeBody(r) @@ -1398,6 +1468,8 @@ func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { } } +// RegisterApp creates a new OAuth2 app to be used with the OAuth2 Provider. On success +// it returns the created app. Must be authenticated as a user. func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { return nil, err @@ -1408,6 +1480,9 @@ func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { } } +// AllowOAuth allows a new session by an OAuth2 App. On success +// it returns the url to be redirected back to the app which initiated the oauth2 flow. +// Must be authenticated as a user. func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) { if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { return nil, err @@ -1418,8 +1493,47 @@ func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (* } } +// GetOAuthAppsByUser returns the OAuth2 Apps registered by the user. On success +// it returns a list of OAuth2 Apps from the same user or all the registered apps if the user +// is a System Administrator. Must be authenticated as a user. +func (c *Client) GetOAuthAppsByUser() (*Result, *AppError) { + if r, err := c.DoApiGet("/oauth/list", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil + } +} + +// GetOAuthAppInfo lookup an OAuth2 App using the client_id. On success +// it returns a Sanitized OAuth2 App. Must be authenticated as a user. +func (c *Client) GetOAuthAppInfo(clientId string) (*Result, *AppError) { + if r, err := c.DoApiGet("/oauth/app/"+clientId, "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil + } +} + +// DeleteOAuthApp deletes an OAuth2 app, the app must be deleted by the same user who created it or +// a System Administrator. On success returs Status OK. Must be authenticated as a user. +func (c *Client) DeleteOAuthApp(id string) (*Result, *AppError) { + data := make(map[string]string) + data["id"] = id + if r, err := c.DoApiPost("/oauth/delete", 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) GetAccessToken(data url.Values) (*Result, *AppError) { - if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil { + if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil { return nil, err } else { defer closeBody(r) @@ -1509,6 +1623,16 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) { } } +// DeletePreferences deletes a list of preferences owned by the current user. If successful, +// it will return status=ok. Otherwise, an error will be returned. +func (c *Client) DeletePreferences(preferences *Preferences) (bool, *AppError) { + if r, err := c.DoApiPost("/preferences/delete", preferences.ToJson()); err != nil { + return false, err + } else { + return c.CheckStatusOK(r), nil + } +} + func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { return nil, err @@ -1648,3 +1772,47 @@ func (c *Client) DeleteEmoji(id string) (bool, *AppError) { func (c *Client) GetCustomEmojiImageUrl(id string) string { return c.GetEmojiRoute() + "/" + id } + +// Uploads a x509 base64 Certificate or Private Key file to be used with SAML. +// data byte array is required and needs to be a Multi-Part with 'certificate' as the field name +// contentType is also required. Returns nil if succesful, otherwise returns an AppError +func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppError { + url := c.ApiUrl + "/admin/add_certificate" + rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) + rq.Header.Set("Content-Type", contentType) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil { + return NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) + } else if rp.StatusCode >= 300 { + return AppErrorFromJson(rp.Body) + } else { + defer closeBody(rp) + return nil + } +} + +// Removes a x509 base64 Certificate or Private Key file used with SAML. +// filename is required. Returns nil if successful, otherwise returns an AppError +func (c *Client) RemoveCertificateFile(filename string) *AppError { + if r, err := c.DoApiPost("/admin/remove_certificate", MapToJson(map[string]string{"filename": filename})); err != nil { + return err + } else { + defer closeBody(r) + return nil + } +} + +// Checks if the x509 base64 Certificates and Private Key files used with SAML exists on the file system. +// Returns a map[string]interface{} if successful, otherwise returns an AppError. Must be System Admin authenticated. +func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, *AppError) { + if r, err := c.DoApiGet("/admin/remove_certificate", "", ""); err != nil { + return nil, err + } else { + defer closeBody(r) + return StringInterfaceFromJson(r.Body), nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/cluster_info.go b/vendor/github.com/mattermost/platform/model/cluster_info.go new file mode 100644 index 00000000..7c3384ae --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/cluster_info.go @@ -0,0 +1,66 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ClusterInfo struct { + Id string `json:"id"` + Version string `json:"version"` + ConfigHash string `json:"config_hash"` + InterNodeUrl string `json:"internode_url"` + Hostname string `json:"hostname"` + LastSuccessfulPing int64 `json:"last_ping"` + IsAlive bool `json:"is_alive"` +} + +func (me *ClusterInfo) ToJson() string { + b, err := json.Marshal(me) + if err != nil { + return "" + } else { + return string(b) + } +} + +func ClusterInfoFromJson(data io.Reader) *ClusterInfo { + decoder := json.NewDecoder(data) + var me ClusterInfo + err := decoder.Decode(&me) + if err == nil { + return &me + } else { + return nil + } +} + +func (me *ClusterInfo) HaveEstablishedInitialContact() bool { + if me.Id != "" { + return true + } + + return false +} + +func ClusterInfosToJson(objmap []*ClusterInfo) string { + if b, err := json.Marshal(objmap); err != nil { + return "" + } else { + return string(b) + } +} + +func ClusterInfosFromJson(data io.Reader) []*ClusterInfo { + decoder := json.NewDecoder(data) + + var objmap []*ClusterInfo + if err := decoder.Decode(&objmap); err != nil { + return make([]*ClusterInfo, 0) + } else { + return objmap + } +} diff --git a/vendor/github.com/mattermost/platform/model/config.go b/vendor/github.com/mattermost/platform/model/config.go index 3a0d7f97..d13ba19e 100644 --- a/vendor/github.com/mattermost/platform/model/config.go +++ b/vendor/github.com/mattermost/platform/model/config.go @@ -6,6 +6,7 @@ package model import ( "encoding/json" "io" + "net/url" ) const ( @@ -22,8 +23,9 @@ const ( PASSWORD_MAXIMUM_LENGTH = 64 PASSWORD_MINIMUM_LENGTH = 5 - SERVICE_GITLAB = "gitlab" - SERVICE_GOOGLE = "google" + SERVICE_GITLAB = "gitlab" + SERVICE_GOOGLE = "google" + SERVICE_OFFICE365 = "office365" WEBSERVER_MODE_REGULAR = "regular" WEBSERVER_MODE_GZIP = "gzip" @@ -44,9 +46,12 @@ const ( RESTRICT_EMOJI_CREATION_ALL = "all" RESTRICT_EMOJI_CREATION_ADMIN = "admin" RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" + + SITENAME_MAX_LENGTH = 30 ) type ServiceSettings struct { + SiteURL *string ListenAddress string MaximumLoginAttempts int SegmentDeveloperKey string @@ -75,6 +80,12 @@ type ServiceSettings struct { RestrictCustomEmojiCreation *string } +type ClusterSettings struct { + Enable *bool + InterNodeListenAddress *string + InterNodeUrls []string +} + type SSOSettings struct { Enable bool Secret string @@ -189,10 +200,12 @@ type TeamSettings struct { RestrictTeamNames *bool EnableCustomBrand *bool CustomBrandText *string + CustomDescriptionText *string RestrictDirectMessage *string RestrictTeamInvite *string RestrictPublicChannelManagement *string RestrictPrivateChannelManagement *string + UserStatusAwayTimeout *int64 } type LdapSettings struct { @@ -265,6 +278,12 @@ type SamlSettings struct { LoginButtonText *string } +type NativeAppSettings struct { + AppDownloadLink *string + AndroidAppDownloadLink *string + IosAppDownloadLink *string +} + type Config struct { ServiceSettings ServiceSettings TeamSettings TeamSettings @@ -278,10 +297,13 @@ type Config struct { SupportSettings SupportSettings GitLabSettings SSOSettings GoogleSettings SSOSettings + Office365Settings SSOSettings LdapSettings LdapSettings ComplianceSettings ComplianceSettings LocalizationSettings LocalizationSettings SamlSettings SamlSettings + NativeAppSettings NativeAppSettings + ClusterSettings ClusterSettings } func (o *Config) ToJson() string { @@ -299,6 +321,8 @@ func (o *Config) GetSSOService(service string) *SSOSettings { return &o.GitLabSettings case SERVICE_GOOGLE: return &o.GoogleSettings + case SERVICE_OFFICE365: + return &o.Office365Settings } return nil @@ -348,6 +372,11 @@ func (o *Config) SetDefaults() { o.EmailSettings.PasswordResetSalt = NewRandomString(32) } + if o.ServiceSettings.SiteURL == nil { + o.ServiceSettings.SiteURL = new(string) + *o.ServiceSettings.SiteURL = "" + } + if o.ServiceSettings.EnableDeveloper == nil { o.ServiceSettings.EnableDeveloper = new(bool) *o.ServiceSettings.EnableDeveloper = false @@ -408,6 +437,11 @@ func (o *Config) SetDefaults() { *o.TeamSettings.CustomBrandText = "" } + if o.TeamSettings.CustomDescriptionText == nil { + o.TeamSettings.CustomDescriptionText = new(string) + *o.TeamSettings.CustomDescriptionText = "" + } + if o.TeamSettings.EnableOpenServer == nil { o.TeamSettings.EnableOpenServer = new(bool) *o.TeamSettings.EnableOpenServer = false @@ -433,6 +467,11 @@ func (o *Config) SetDefaults() { *o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL } + if o.TeamSettings.UserStatusAwayTimeout == nil { + o.TeamSettings.UserStatusAwayTimeout = new(int64) + *o.TeamSettings.UserStatusAwayTimeout = 300 + } + if o.EmailSettings.EnableSignInWithEmail == nil { o.EmailSettings.EnableSignInWithEmail = new(bool) @@ -474,7 +513,7 @@ func (o *Config) SetDefaults() { if o.SupportSettings.TermsOfServiceLink == nil { o.SupportSettings.TermsOfServiceLink = new(string) - *o.SupportSettings.TermsOfServiceLink = "/static/help/terms.html" + *o.SupportSettings.TermsOfServiceLink = "https://about.mattermost.com/default-terms/" } if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) { @@ -483,7 +522,7 @@ func (o *Config) SetDefaults() { if o.SupportSettings.PrivacyPolicyLink == nil { o.SupportSettings.PrivacyPolicyLink = new(string) - *o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html" + *o.SupportSettings.PrivacyPolicyLink = "" } if !IsSafeLink(o.SupportSettings.AboutLink) { @@ -492,7 +531,7 @@ func (o *Config) SetDefaults() { if o.SupportSettings.AboutLink == nil { o.SupportSettings.AboutLink = new(string) - *o.SupportSettings.AboutLink = "/static/help/about.html" + *o.SupportSettings.AboutLink = "" } if !IsSafeLink(o.SupportSettings.HelpLink) { @@ -501,7 +540,7 @@ func (o *Config) SetDefaults() { if o.SupportSettings.HelpLink == nil { o.SupportSettings.HelpLink = new(string) - *o.SupportSettings.HelpLink = "/static/help/help.html" + *o.SupportSettings.HelpLink = "" } if !IsSafeLink(o.SupportSettings.ReportAProblemLink) { @@ -510,7 +549,7 @@ func (o *Config) SetDefaults() { if o.SupportSettings.ReportAProblemLink == nil { o.SupportSettings.ReportAProblemLink = new(string) - *o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html" + *o.SupportSettings.ReportAProblemLink = "" } if o.SupportSettings.SupportEmail == nil { @@ -675,6 +714,20 @@ func (o *Config) SetDefaults() { *o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL } + if o.ClusterSettings.InterNodeListenAddress == nil { + o.ClusterSettings.InterNodeListenAddress = new(string) + *o.ClusterSettings.InterNodeListenAddress = ":8075" + } + + if o.ClusterSettings.Enable == nil { + o.ClusterSettings.Enable = new(bool) + *o.ClusterSettings.Enable = false + } + + if o.ClusterSettings.InterNodeUrls == nil { + o.ClusterSettings.InterNodeUrls = []string{} + } + if o.ComplianceSettings.Enable == nil { o.ComplianceSettings.Enable = new(bool) *o.ComplianceSettings.Enable = false @@ -784,6 +837,21 @@ func (o *Config) SetDefaults() { o.SamlSettings.LocaleAttribute = new(string) *o.SamlSettings.LocaleAttribute = "" } + + if o.NativeAppSettings.AppDownloadLink == nil { + o.NativeAppSettings.AppDownloadLink = new(string) + *o.NativeAppSettings.AppDownloadLink = "https://about.mattermost.com/downloads/" + } + + if o.NativeAppSettings.AndroidAppDownloadLink == nil { + o.NativeAppSettings.AndroidAppDownloadLink = new(string) + *o.NativeAppSettings.AndroidAppDownloadLink = "https://about.mattermost.com/mattermost-android-app/" + } + + if o.NativeAppSettings.IosAppDownloadLink == nil { + o.NativeAppSettings.IosAppDownloadLink = new(string) + *o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/" + } } func (o *Config) IsValid() *AppError { @@ -792,6 +860,12 @@ func (o *Config) IsValid() *AppError { return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "") } + if len(*o.ServiceSettings.SiteURL) != 0 { + if _, err := url.ParseRequestURI(*o.ServiceSettings.SiteURL); err != nil { + return NewLocAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "") + } + } + if len(o.ServiceSettings.ListenAddress) == 0 { return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "") } @@ -893,21 +967,37 @@ func (o *Config) IsValid() *AppError { } if *o.LdapSettings.Enable { - if *o.LdapSettings.LdapServer == "" || - *o.LdapSettings.BaseDN == "" || - *o.LdapSettings.BindUsername == "" || - *o.LdapSettings.BindPassword == "" || - *o.LdapSettings.FirstNameAttribute == "" || - *o.LdapSettings.LastNameAttribute == "" || - *o.LdapSettings.EmailAttribute == "" || - *o.LdapSettings.UsernameAttribute == "" || - *o.LdapSettings.IdAttribute == "" { - return NewLocAppError("Config.IsValid", "Required LDAP field missing", nil, "") + if *o.LdapSettings.LdapServer == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "") + } + + if *o.LdapSettings.BaseDN == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "") + } + + if *o.LdapSettings.FirstNameAttribute == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_firstname", nil, "") + } + + if *o.LdapSettings.LastNameAttribute == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_lastname", nil, "") + } + + if *o.LdapSettings.EmailAttribute == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_email", nil, "") + } + + if *o.LdapSettings.UsernameAttribute == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_username", nil, "") + } + + if *o.LdapSettings.IdAttribute == "" { + return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "") } } if *o.SamlSettings.Enable { - if len(*o.SamlSettings.IdpUrl) == 0 { + if len(*o.SamlSettings.IdpUrl) == 0 || !IsValidHttpUrl(*o.SamlSettings.IdpUrl) { return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "") } @@ -960,6 +1050,10 @@ func (o *Config) IsValid() *AppError { return NewLocAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "") } + if len(o.TeamSettings.SiteName) > SITENAME_MAX_LENGTH { + return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "") + } + return nil } diff --git a/vendor/github.com/mattermost/platform/model/job.go b/vendor/github.com/mattermost/platform/model/job.go index b6c68dce..229d5efd 100644 --- a/vendor/github.com/mattermost/platform/model/job.go +++ b/vendor/github.com/mattermost/platform/model/job.go @@ -84,6 +84,12 @@ func (task *ScheduledTask) Cancel() { removeTaskByName(task.Name) } +// Executes the task immediatly. A recurring task will be run regularally after interval. +func (task *ScheduledTask) Execute() { + task.function() + task.timer.Reset(task.Interval) +} + func (task *ScheduledTask) String() string { return fmt.Sprintf( "%s\nInterval: %s\nRecurring: %t\n", diff --git a/vendor/github.com/mattermost/platform/model/license.go b/vendor/github.com/mattermost/platform/model/license.go index a77a7349..98b88fde 100644 --- a/vendor/github.com/mattermost/platform/model/license.go +++ b/vendor/github.com/mattermost/platform/model/license.go @@ -35,8 +35,10 @@ type Features struct { Users *int `json:"users"` LDAP *bool `json:"ldap"` MFA *bool `json:"mfa"` - GoogleSSO *bool `json:"google_sso"` + GoogleOAuth *bool `json:"google_oauth"` + Office365OAuth *bool `json:"office365_oauth"` Compliance *bool `json:"compliance"` + Cluster *bool `json:"cluster"` CustomBrand *bool `json:"custom_brand"` MHPNS *bool `json:"mhpns"` SAML *bool `json:"saml"` @@ -65,9 +67,14 @@ func (f *Features) SetDefaults() { *f.MFA = *f.FutureFeatures } - if f.GoogleSSO == nil { - f.GoogleSSO = new(bool) - *f.GoogleSSO = *f.FutureFeatures + if f.GoogleOAuth == nil { + f.GoogleOAuth = new(bool) + *f.GoogleOAuth = *f.FutureFeatures + } + + if f.Office365OAuth == nil { + f.Office365OAuth = new(bool) + *f.Office365OAuth = *f.FutureFeatures } if f.Compliance == nil { @@ -75,6 +82,11 @@ func (f *Features) SetDefaults() { *f.Compliance = *f.FutureFeatures } + if f.Cluster == nil { + f.Cluster = new(bool) + *f.Cluster = *f.FutureFeatures + } + if f.CustomBrand == nil { f.CustomBrand = new(bool) *f.CustomBrand = *f.FutureFeatures diff --git a/vendor/github.com/mattermost/platform/model/message.go b/vendor/github.com/mattermost/platform/model/message.go deleted file mode 100644 index 12f3be66..00000000 --- a/vendor/github.com/mattermost/platform/model/message.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - ACTION_TYPING = "typing" - ACTION_POSTED = "posted" - ACTION_POST_EDITED = "post_edited" - ACTION_POST_DELETED = "post_deleted" - ACTION_CHANNEL_DELETED = "channel_deleted" - ACTION_CHANNEL_VIEWED = "channel_viewed" - ACTION_DIRECT_ADDED = "direct_added" - ACTION_NEW_USER = "new_user" - ACTION_LEAVE_TEAM = "leave_team" - ACTION_USER_ADDED = "user_added" - ACTION_USER_REMOVED = "user_removed" - ACTION_PREFERENCE_CHANGED = "preference_changed" - ACTION_EPHEMERAL_MESSAGE = "ephemeral_message" -) - -type Message struct { - TeamId string `json:"team_id"` - ChannelId string `json:"channel_id"` - UserId string `json:"user_id"` - Action string `json:"action"` - Props map[string]string `json:"props"` -} - -func (m *Message) Add(key string, value string) { - m.Props[key] = value -} - -func NewMessage(teamId string, channelId string, userId string, action string) *Message { - return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)} -} - -func (o *Message) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func MessageFromJson(data io.Reader) *Message { - decoder := json.NewDecoder(data) - var o Message - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/oauth.go b/vendor/github.com/mattermost/platform/model/oauth.go index c54df107..cfe643c9 100644 --- a/vendor/github.com/mattermost/platform/model/oauth.go +++ b/vendor/github.com/mattermost/platform/model/oauth.go @@ -25,8 +25,10 @@ type OAuthApp struct { ClientSecret string `json:"client_secret"` Name string `json:"name"` Description string `json:"description"` + IconURL string `json:"icon_url"` CallbackUrls StringArray `json:"callback_urls"` Homepage string `json:"homepage"` + IsTrusted bool `json:"is_trusted"` } // IsValid validates the app and returns an error if it isn't configured @@ -61,7 +63,13 @@ func (a *OAuthApp) IsValid() *AppError { return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) } - if len(a.Homepage) == 0 || len(a.Homepage) > 256 { + for _, callback := range a.CallbackUrls { + if !IsValidHttpUrl(callback) { + return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "") + } + } + + if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id) } @@ -69,6 +77,12 @@ func (a *OAuthApp) IsValid() *AppError { return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) } + if len(a.IconURL) > 0 { + if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { + return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id) + } + } + return nil } @@ -85,10 +99,6 @@ func (a *OAuthApp) PreSave() { a.CreateAt = GetMillis() a.UpdateAt = a.CreateAt - - if len(a.ClientSecret) > 0 { - a.ClientSecret = HashPassword(a.ClientSecret) - } } // PreUpdate should be run before updating the app in the db. @@ -157,3 +167,23 @@ func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp { return nil } } + +func OAuthAppListToJson(l []*OAuthApp) string { + b, err := json.Marshal(l) + if err != nil { + return "" + } else { + return string(b) + } +} + +func OAuthAppListFromJson(data io.Reader) []*OAuthApp { + decoder := json.NewDecoder(data) + var o []*OAuthApp + err := decoder.Decode(&o) + if err == nil { + return o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/outgoing_webhook.go b/vendor/github.com/mattermost/platform/model/outgoing_webhook.go index ee7a32f1..ec2ed75c 100644 --- a/vendor/github.com/mattermost/platform/model/outgoing_webhook.go +++ b/vendor/github.com/mattermost/platform/model/outgoing_webhook.go @@ -9,6 +9,7 @@ import ( "io" "net/url" "strconv" + "strings" ) type OutgoingWebhook struct { @@ -21,6 +22,7 @@ type OutgoingWebhook struct { ChannelId string `json:"channel_id"` TeamId string `json:"team_id"` TriggerWords StringArray `json:"trigger_words"` + TriggerWhen int `json:"trigger_when"` CallbackURLs StringArray `json:"callback_urls"` DisplayName string `json:"display_name"` Description string `json:"description"` @@ -171,6 +173,10 @@ func (o *OutgoingWebhook) IsValid() *AppError { return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") } + if o.TriggerWhen > 1 { + return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") + } + return nil } @@ -204,3 +210,17 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool { return false } + +func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { + if len(o.TriggerWords) == 0 || len(word) == 0 { + return false + } + + for _, trigger := range o.TriggerWords { + if strings.HasPrefix(word, trigger) { + return true + } + } + + return false +} diff --git a/vendor/github.com/mattermost/platform/model/post.go b/vendor/github.com/mattermost/platform/model/post.go index 173c99e3..175aecdd 100644 --- a/vendor/github.com/mattermost/platform/model/post.go +++ b/vendor/github.com/mattermost/platform/model/post.go @@ -162,9 +162,6 @@ func (o *Post) AddProp(key string, value interface{}) { o.Props[key] = value } -func (o *Post) PreExport() { -} - func (o *Post) IsSystemMessage() bool { return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX } diff --git a/vendor/github.com/mattermost/platform/model/preference.go b/vendor/github.com/mattermost/platform/model/preference.go index 22858e04..5787fe6e 100644 --- a/vendor/github.com/mattermost/platform/model/preference.go +++ b/vendor/github.com/mattermost/platform/model/preference.go @@ -6,6 +6,8 @@ package model import ( "encoding/json" "io" + "regexp" + "strings" "unicode/utf8" ) @@ -13,10 +15,17 @@ const ( PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show" PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step" PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings" + PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post" PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews" + PREFERENCE_CATEGORY_THEME = "theme" + // the name for theme props is the team id + + PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app" + // the name for oauth_app is the client_id and value is the current scope + PREFERENCE_CATEGORY_LAST = "last" PREFERENCE_NAME_LAST_CHANNEL = "channel" ) @@ -57,13 +66,48 @@ func (o *Preference) IsValid() *AppError { return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category) } - if len(o.Name) == 0 || len(o.Name) > 32 { + if len(o.Name) > 32 { return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name) } - if utf8.RuneCountInString(o.Value) > 128 { + if utf8.RuneCountInString(o.Value) > 2000 { return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value) } + if o.Category == PREFERENCE_CATEGORY_THEME { + var unused map[string]string + if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil { + return NewLocAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value) + } + } + return nil } + +func (o *Preference) PreUpdate() { + if o.Category == PREFERENCE_CATEGORY_THEME { + // decode the value of theme (a map of strings to string) and eliminate any invalid values + var props map[string]string + if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil { + // just continue, the invalid preference value should get caught by IsValid before saving + return + } + + colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) + + // blank out any invalid theme values + for name, value := range props { + if name == "image" || name == "type" || name == "codeTheme" { + continue + } + + if !colorPattern.MatchString(value) { + props[name] = "#ffffff" + } + } + + if b, err := json.Marshal(props); err == nil { + o.Value = string(b) + } + } +} diff --git a/vendor/github.com/mattermost/platform/model/session.go b/vendor/github.com/mattermost/platform/model/session.go index 8a5eec74..ef51374d 100644 --- a/vendor/github.com/mattermost/platform/model/session.go +++ b/vendor/github.com/mattermost/platform/model/session.go @@ -83,7 +83,11 @@ func (me *Session) IsExpired() bool { } func (me *Session) SetExpireInDays(days int) { - me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) + if me.CreateAt == 0 { + me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) + } else { + me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days)) + } } func (me *Session) AddProp(key string, value string) { diff --git a/vendor/github.com/mattermost/platform/model/status.go b/vendor/github.com/mattermost/platform/model/status.go new file mode 100644 index 00000000..8bf26f2f --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/status.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + STATUS_OFFLINE = "offline" + STATUS_AWAY = "away" + STATUS_ONLINE = "online" + STATUS_CACHE_SIZE = 10000 +) + +type Status struct { + UserId string `json:"user_id"` + Status string `json:"status"` + LastActivityAt int64 `json:"last_activity_at"` +} + +func (o *Status) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func StatusFromJson(data io.Reader) *Status { + decoder := json.NewDecoder(data) + var o Status + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/team.go b/vendor/github.com/mattermost/platform/model/team.go index 072e0a8c..dccc0219 100644 --- a/vendor/github.com/mattermost/platform/model/team.go +++ b/vendor/github.com/mattermost/platform/model/team.go @@ -224,9 +224,6 @@ func CleanTeamName(s string) string { return s } -func (o *Team) PreExport() { -} - func (o *Team) Sanitize() { o.Email = "" o.AllowedDomains = "" diff --git a/vendor/github.com/mattermost/platform/model/user.go b/vendor/github.com/mattermost/platform/model/user.go index c792f80d..bad616d7 100644 --- a/vendor/github.com/mattermost/platform/model/user.go +++ b/vendor/github.com/mattermost/platform/model/user.go @@ -16,11 +16,6 @@ import ( const ( ROLE_SYSTEM_ADMIN = "system_admin" - USER_AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutes - USER_OFFLINE_TIMEOUT = 1 * 60 * 1000 // 1 minute - USER_OFFLINE = "offline" - USER_AWAY = "away" - USER_ONLINE = "online" USER_NOTIFY_ALL = "all" USER_NOTIFY_MENTION = "mention" USER_NOTIFY_NONE = "none" @@ -44,12 +39,9 @@ type User struct { FirstName string `json:"first_name"` LastName string `json:"last_name"` Roles string `json:"roles"` - LastActivityAt int64 `json:"last_activity_at,omitempty"` - LastPingAt int64 `json:"last_ping_at,omitempty"` AllowMarketing bool `json:"allow_marketing,omitempty"` Props StringMap `json:"props,omitempty"` NotifyProps StringMap `json:"notify_props,omitempty"` - ThemeProps StringMap `json:"theme_props,omitempty"` LastPasswordUpdate int64 `json:"last_password_update,omitempty"` LastPictureUpdate int64 `json:"last_picture_update,omitempty"` FailedAttempts int `json:"failed_attempts,omitempty"` @@ -106,10 +98,6 @@ func (u *User) IsValid() *AppError { return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id) } - if len(u.ThemeProps) > 2000 { - return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id) - } - return nil } @@ -179,21 +167,6 @@ func (u *User) PreUpdate() { } u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",") } - - if u.ThemeProps != nil { - colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) - - // blank out any invalid theme values - for name, value := range u.ThemeProps { - if name == "image" || name == "type" || name == "codeTheme" { - continue - } - - if !colorPattern.MatchString(value) { - u.ThemeProps[name] = "#ffffff" - } - } - } } func (u *User) SetDefaultNotifications() { @@ -242,14 +215,6 @@ func (u *User) Etag(showFullName, showEmail bool) string { return Etag(u.Id, u.UpdateAt, showFullName, showEmail) } -func (u *User) IsOffline() bool { - return (GetMillis()-u.LastPingAt) > USER_OFFLINE_TIMEOUT && (GetMillis()-u.LastActivityAt) > USER_OFFLINE_TIMEOUT -} - -func (u *User) IsAway() bool { - return (GetMillis() - u.LastActivityAt) > USER_AWAY_TIMEOUT -} - // Remove any private data from the user object func (u *User) Sanitize(options map[string]bool) { u.Password = "" @@ -278,11 +243,9 @@ func (u *User) ClearNonProfileFields() { u.MfaActive = false u.MfaSecret = "" u.EmailVerified = false - u.LastPingAt = 0 u.AllowMarketing = false u.Props = StringMap{} u.NotifyProps = StringMap{} - u.ThemeProps = StringMap{} u.LastPasswordUpdate = 0 u.LastPictureUpdate = 0 u.FailedAttempts = 0 @@ -392,17 +355,6 @@ func (u *User) IsLDAPUser() bool { return false } -func (u *User) PreExport() { - u.Password = "" - u.AuthData = new(string) - *u.AuthData = "" - u.LastActivityAt = 0 - u.LastPingAt = 0 - u.LastPasswordUpdate = 0 - u.LastPictureUpdate = 0 - u.FailedAttempts = 0 -} - // UserFromJson will decode the input and return a User func UserFromJson(data io.Reader) *User { decoder := json.NewDecoder(data) @@ -461,6 +413,7 @@ var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`) var restrictedUsernames = []string{ "all", "channel", + "matterbot", } func IsValidUsername(s string) bool { diff --git a/vendor/github.com/mattermost/platform/model/utils.go b/vendor/github.com/mattermost/platform/model/utils.go index 27ab3e27..a4a4208c 100644 --- a/vendor/github.com/mattermost/platform/model/utils.go +++ b/vendor/github.com/mattermost/platform/model/utils.go @@ -34,12 +34,12 @@ type EncryptStringMap map[string]string type AppError struct { Id string `json:"id"` - Message string `json:"message"` // Message to be display to the end user without debugging information - DetailedError string `json:"detailed_error"` // Internal error string to help the developer - RequestId string `json:"request_id"` // The RequestId that's also set in the header - StatusCode int `json:"status_code"` // The http status code - Where string `json:"-"` // The function where it happened in the form of Struct.Func - IsOAuth bool `json:"is_oauth"` // Whether the error is OAuth specific + Message string `json:"message"` // Message to be display to the end user without debugging information + DetailedError string `json:"detailed_error"` // Internal error string to help the developer + RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header + StatusCode int `json:"status_code,omitempty"` // The http status code + Where string `json:"-"` // The function where it happened in the form of Struct.Func + IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific params map[string]interface{} `json:"-"` } diff --git a/vendor/github.com/mattermost/platform/model/version.go b/vendor/github.com/mattermost/platform/model/version.go index c6f49525..49e8af04 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.3.0", "3.2.0", "3.1.0", "3.0.0", diff --git a/vendor/github.com/mattermost/platform/model/websocket_client.go b/vendor/github.com/mattermost/platform/model/websocket_client.go new file mode 100644 index 00000000..a048bd85 --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/websocket_client.go @@ -0,0 +1,109 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "github.com/gorilla/websocket" + "net/http" +) + +type WebSocketClient struct { + Url string // The location of the server like "ws://localhost:8065" + ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3" + Conn *websocket.Conn // The WebSocket connection + AuthToken string // The token used to open the WebSocket + Sequence int64 // The ever-incrementing sequence attached to each WebSocket action + EventChannel chan *WebSocketEvent + ResponseChannel chan *WebSocketResponse +} + +// 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) + if err != nil { + return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) + } + + return &WebSocketClient{ + url, + url + API_URL_SUFFIX, + conn, + authToken, + 1, + make(chan *WebSocketEvent, 100), + make(chan *WebSocketResponse, 100), + }, 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) + if err != nil { + return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) + } + + return nil +} + +func (wsc *WebSocketClient) Close() { + wsc.Conn.Close() +} + +func (wsc *WebSocketClient) Listen() { + go func() { + for { + var rawMsg json.RawMessage + var err error + if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { + return + } + + var event WebSocketEvent + if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { + wsc.EventChannel <- &event + continue + } + + var response WebSocketResponse + if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { + wsc.ResponseChannel <- &response + continue + } + } + }() +} + +func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { + req := &WebSocketRequest{} + req.Seq = wsc.Sequence + req.Action = action + req.Data = data + + wsc.Sequence++ + + wsc.Conn.WriteJSON(req) +} + +// UserTyping will push a user_typing event out to all connected users +// who are in the specified channel +func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { + data := map[string]interface{}{ + "channel_id": channelId, + "parent_id": parentId, + } + + wsc.SendMessage("user_typing", data) +} + +// GetStatuses will return a map of string statuses using user id as the key +func (wsc *WebSocketClient) GetStatuses() { + wsc.SendMessage("get_statuses", nil) +} diff --git a/vendor/github.com/mattermost/platform/model/websocket_message.go b/vendor/github.com/mattermost/platform/model/websocket_message.go new file mode 100644 index 00000000..ae9a140c --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/websocket_message.go @@ -0,0 +1,114 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + WEBSOCKET_EVENT_TYPING = "typing" + WEBSOCKET_EVENT_POSTED = "posted" + WEBSOCKET_EVENT_POST_EDITED = "post_edited" + WEBSOCKET_EVENT_POST_DELETED = "post_deleted" + WEBSOCKET_EVENT_CHANNEL_DELETED = "channel_deleted" + WEBSOCKET_EVENT_CHANNEL_VIEWED = "channel_viewed" + WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added" + WEBSOCKET_EVENT_NEW_USER = "new_user" + WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team" + WEBSOCKET_EVENT_USER_ADDED = "user_added" + WEBSOCKET_EVENT_USER_REMOVED = "user_removed" + WEBSOCKET_EVENT_PREFERENCE_CHANGED = "preference_changed" + WEBSOCKET_EVENT_EPHEMERAL_MESSAGE = "ephemeral_message" + WEBSOCKET_EVENT_STATUS_CHANGE = "status_change" +) + +type WebSocketMessage interface { + ToJson() string + IsValid() bool +} + +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"` +} + +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 (o *WebSocketEvent) IsValid() bool { + return o.Event != "" +} + +func (o *WebSocketEvent) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { + decoder := json.NewDecoder(data) + var o WebSocketEvent + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} + +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"` +} + +func (m *WebSocketResponse) Add(key string, value interface{}) { + m.Data[key] = value +} + +func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse { + return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data} +} + +func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse { + return &WebSocketResponse{Status: STATUS_FAIL, SeqReply: seqReply, Error: err} +} + +func (o *WebSocketResponse) IsValid() bool { + return o.Status != "" +} + +func (o *WebSocketResponse) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { + decoder := json.NewDecoder(data) + var o WebSocketResponse + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/github.com/mattermost/platform/model/websocket_request.go b/vendor/github.com/mattermost/platform/model/websocket_request.go new file mode 100644 index 00000000..d0f35f68 --- /dev/null +++ b/vendor/github.com/mattermost/platform/model/websocket_request.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type WebSocketRequest struct { + // Client-provided fields + Seq int64 `json:"seq"` + Action string `json:"action"` + Data map[string]interface{} `json:"data"` + + // Server-provided fields + Session Session `json:"-"` + T goi18n.TranslateFunc `json:"-"` + Locale string `json:"-"` +} + +func (o *WebSocketRequest) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func WebSocketRequestFromJson(data io.Reader) *WebSocketRequest { + decoder := json.NewDecoder(data) + var o WebSocketRequest + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/vendor/manifest b/vendor/manifest index 98c01162..1fe265ac 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -63,8 +63,8 @@ "importpath": "github.com/mattermost/platform/einterfaces", "repository": "https://github.com/mattermost/platform", "vcs": "git", - "revision": "ab52700aaa76a5623de23cd0156f5dbd9a24e264", - "branch": "release-3.2", + "revision": "20735470185e0b0ac1d15b975041ed9a2e0e43bc", + "branch": "release-3.3", "path": "/einterfaces", "notests": true }, @@ -72,8 +72,8 @@ "importpath": "github.com/mattermost/platform/model", "repository": "https://github.com/mattermost/platform", "vcs": "git", - "revision": "ab52700aaa76a5623de23cd0156f5dbd9a24e264", - "branch": "release-3.2", + "revision": "20735470185e0b0ac1d15b975041ed9a2e0e43bc", + "branch": "release-3.3", "path": "/model", "notests": true }, @@ -179,4 +179,4 @@ "notests": true } ] -} +} \ No newline at end of file