4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-07-03 04:57:44 +00:00

Update dependencies (#1610)

* Update dependencies

* Update module to go 1.17
This commit is contained in:
Wim
2021-10-17 00:47:22 +02:00
committed by GitHub
parent 7ae45c42e7
commit 4dd8bae5c9
494 changed files with 15698 additions and 19720 deletions

View File

@ -4813,3 +4813,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
## JWT-Go
This product contains `jwt-go` by Dave Grijalva
* HOMEPAGE:
* https://github.com/dgrijalva/jwt-go
* LICENSE:
The MIT License (MIT)
Copyright (c) 2012 Dave Grijalva
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -31,17 +31,18 @@ type AccessResponse struct {
ExpiresIn int32 `json:"expires_in"`
Scope string `json:"scope"`
RefreshToken string `json:"refresh_token"`
IdToken string `json:"id_token"`
}
// IsValid validates the AccessData and returns an error if it isn't configured
// correctly.
func (ad *AccessData) IsValid() *AppError {
if len(ad.ClientId) == 0 || len(ad.ClientId) > 26 {
if ad.ClientId == "" || len(ad.ClientId) > 26 {
return NewAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.UserId) == 0 || len(ad.UserId) > 26 {
if ad.UserId == "" || len(ad.UserId) > 26 {
return NewAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
@ -53,7 +54,7 @@ func (ad *AccessData) IsValid() *AppError {
return NewAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
if ad.RedirectUri == "" || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "", http.StatusBadRequest)
}

View File

@ -15,27 +15,27 @@ type AnalyticsRow struct {
type AnalyticsRows []*AnalyticsRow
func (me *AnalyticsRow) ToJson() string {
b, _ := json.Marshal(me)
func (ar *AnalyticsRow) ToJson() string {
b, _ := json.Marshal(ar)
return string(b)
}
func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow {
var me *AnalyticsRow
json.NewDecoder(data).Decode(&me)
return me
var ar *AnalyticsRow
json.NewDecoder(data).Decode(&ar)
return ar
}
func (me AnalyticsRows) ToJson() string {
if b, err := json.Marshal(me); err != nil {
func (ar AnalyticsRows) ToJson() string {
b, err := json.Marshal(ar)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows {
var me AnalyticsRows
json.NewDecoder(data).Decode(&me)
return me
var ar AnalyticsRows
json.NewDecoder(data).Decode(&ar)
return ar
}

View File

@ -8,7 +8,7 @@ import (
"strings"
)
var atMentionRegexp = regexp.MustCompile(`\B@[[:alnum:]][[:alnum:]\.\-_]*`)
var atMentionRegexp = regexp.MustCompile(`\B@[[:alnum:]][[:alnum:]\.\-_:]*`)
const usernameSpecialChars = ".-_"
@ -24,7 +24,7 @@ func PossibleAtMentions(message string) []string {
alreadyMentioned := make(map[string]bool)
for _, match := range atMentionRegexp.FindAllString(message, -1) {
name := NormalizeUsername(match[1:])
if !alreadyMentioned[name] && IsValidUsername(name) {
if !alreadyMentioned[name] && IsValidUsernameAllowRemote(name) {
names = append(names, name)
alreadyMentioned[name] = true
}

View File

@ -3,7 +3,9 @@
package model
import "github.com/francoispqt/gojay"
import (
"github.com/francoispqt/gojay"
)
// AuditModelTypeConv converts key model types to something better suited for audit output.
func AuditModelTypeConv(val interface{}) (newVal interface{}, converted bool) {
@ -49,6 +51,8 @@ func AuditModelTypeConv(val interface{}) (newVal interface{}, converted bool) {
return newAuditIncomingWebhook(v), true
case *OutgoingWebhook:
return newAuditOutgoingWebhook(v), true
case *RemoteCluster:
return newRemoteCluster(v), true
}
return val, false
}
@ -665,3 +669,45 @@ func (h auditOutgoingWebhook) MarshalJSONObject(enc *gojay.Encoder) {
func (h auditOutgoingWebhook) IsNil() bool {
return false
}
type auditRemoteCluster struct {
RemoteId string
RemoteTeamId string
Name string
DisplayName string
SiteURL string
CreateAt int64
LastPingAt int64
CreatorId string
}
// newRemoteCluster creates a simplified representation of RemoteCluster for output to audit log.
func newRemoteCluster(r *RemoteCluster) auditRemoteCluster {
var rc auditRemoteCluster
if r != nil {
rc.RemoteId = r.RemoteId
rc.RemoteTeamId = r.RemoteTeamId
rc.Name = r.Name
rc.DisplayName = r.DisplayName
rc.SiteURL = r.SiteURL
rc.CreateAt = r.CreateAt
rc.LastPingAt = r.LastPingAt
rc.CreatorId = r.CreatorId
}
return rc
}
func (r auditRemoteCluster) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("remote_id", r.RemoteId)
enc.StringKey("remote_team_id", r.RemoteTeamId)
enc.StringKey("name", r.Name)
enc.StringKey("display_name", r.DisplayName)
enc.StringKey("site_url", r.SiteURL)
enc.Int64Key("create_at", r.CreateAt)
enc.Int64Key("last_ping_at", r.LastPingAt)
enc.StringKey("creator_id", r.CreatorId)
}
func (r auditRemoteCluster) IsNil() bool {
return false
}

View File

@ -14,17 +14,16 @@ func (o Audits) Etag() string {
if len(o) > 0 {
// the first in the list is always the most current
return Etag(o[0].CreateAt)
} else {
return ""
}
return ""
}
func (o Audits) ToJson() string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func AuditsFromJson(data io.Reader) Audits {

View File

@ -47,7 +47,7 @@ func (ad *AuthData) IsValid() *AppError {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.Code) == 0 || len(ad.Code) > 128 {
if ad.Code == "" || len(ad.Code) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.auth_code.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
@ -82,11 +82,11 @@ func (ar *AuthorizeRequest) IsValid() *AppError {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.ResponseType) == 0 {
if ar.ResponseType == "" {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) {
if ar.RedirectUri == "" || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
@ -110,7 +110,7 @@ func (ad *AuthData) PreSave() {
ad.CreateAt = GetMillis()
}
if len(ad.Scope) == 0 {
if ad.Scope == "" {
ad.Scope = DEFAULT_SCOPE
}
}

View File

@ -17,6 +17,7 @@ const (
BOT_DESCRIPTION_MAX_RUNES = 1024
BOT_CREATOR_ID_MAX_RUNES = KEY_VALUE_PLUGIN_ID_MAX_RUNES // UserId or PluginId
BOT_WARN_METRIC_BOT_USERNAME = "mattermost-advisor"
BOT_SYSTEM_BOT_USERNAME = "system-bot"
)
// Bot is a special type of User meant for programmatic interactions.
@ -82,7 +83,7 @@ func (b *Bot) IsValid() *AppError {
return NewAppError("Bot.IsValid", "model.bot.is_valid.description.app_error", b.Trace(), "", http.StatusBadRequest)
}
if len(b.OwnerId) == 0 || utf8.RuneCountInString(b.OwnerId) > BOT_CREATOR_ID_MAX_RUNES {
if b.OwnerId == "" || utf8.RuneCountInString(b.OwnerId) > BOT_CREATOR_ID_MAX_RUNES {
return NewAppError("Bot.IsValid", "model.bot.is_valid.creator_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
@ -128,6 +129,8 @@ func BotFromJson(data io.Reader) *Bot {
}
// Patch modifies an existing bot with optional fields from the given patch.
// TODO 6.0: consider returning a boolean to indicate whether or not the patch
// applied any changes.
func (b *Bot) Patch(patch *BotPatch) {
if patch.Username != nil {
b.Username = *patch.Username
@ -142,6 +145,23 @@ func (b *Bot) Patch(patch *BotPatch) {
}
}
// WouldPatch returns whether or not the given patch would be applied or not.
func (b *Bot) WouldPatch(patch *BotPatch) bool {
if patch == nil {
return false
}
if patch.Username != nil && *patch.Username != b.Username {
return true
}
if patch.DisplayName != nil && *patch.DisplayName != b.DisplayName {
return true
}
if patch.Description != nil && *patch.Description != b.Description {
return true
}
return false
}
// ToJson serializes the bot patch to json.
func (b *BotPatch) ToJson() []byte {
data, err := json.Marshal(b)

View File

@ -1,288 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
var BotDefaultImage = []byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7e,
0x08, 0x06, 0x00, 0x00, 0x00, 0xec, 0xa6, 0x19, 0xa2, 0x00, 0x00, 0x00,
0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61,
0x05, 0x00, 0x00, 0x0c, 0xe3, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xed,
0x5d, 0x5b, 0x88, 0x15, 0xc9, 0x19, 0xae, 0xf1, 0xba, 0x5e, 0xc6, 0x4b,
0xbc, 0xa0, 0x46, 0xd7, 0x2b, 0xce, 0x2a, 0xba, 0xca, 0x8a, 0xa3, 0x82,
0x38, 0xa3, 0x46, 0x47, 0x5d, 0x59, 0xa3, 0x2f, 0x9b, 0x28, 0x6e, 0xc2,
0xea, 0x83, 0x22, 0x04, 0x82, 0x78, 0x41, 0xf2, 0xb2, 0x04, 0xd4, 0x07,
0x49, 0x22, 0xc4, 0x07, 0xcd, 0x53, 0x30, 0x26, 0x1a, 0xa2, 0x08, 0xee,
0xaa, 0xa8, 0x71, 0xb2, 0x41, 0x11, 0xbc, 0xdf, 0xd0, 0x8d, 0x6e, 0x24,
0x82, 0xe3, 0x65, 0x32, 0x9b, 0x19, 0x1d, 0x47, 0xd7, 0xeb, 0xc9, 0xf7,
0xb5, 0xa7, 0x0f, 0xe7, 0x9c, 0xe9, 0x3e, 0xdd, 0xe7, 0x4c, 0x57, 0x77,
0x75, 0x75, 0xfd, 0xf0, 0x9f, 0xee, 0xae, 0xaa, 0xae, 0xfa, 0xff, 0xef,
0xfb, 0x4f, 0x9d, 0xee, 0xaa, 0xea, 0x3e, 0x65, 0x42, 0x4f, 0xe9, 0x0b,
0xb7, 0x46, 0x43, 0x2b, 0xd2, 0xdb, 0x91, 0xd8, 0xf6, 0x84, 0x96, 0x67,
0x69, 0xf7, 0xf4, 0x3e, 0x36, 0xa2, 0x19, 0xfa, 0x34, 0xbd, 0xe5, 0x3e,
0xf5, 0x31, 0xf4, 0xdf, 0xd0, 0x5b, 0xd0, 0x7f, 0xa5, 0xb7, 0x0d, 0xd8,
0x6a, 0x25, 0x65, 0x1a, 0x78, 0xd3, 0x1b, 0x3e, 0x54, 0x41, 0x67, 0x42,
0x2b, 0xa1, 0x24, 0x9d, 0x69, 0x32, 0xa4, 0x11, 0x95, 0x32, 0x18, 0xce,
0x42, 0x6b, 0xa1, 0x5f, 0x43, 0x99, 0x16, 0x5b, 0x89, 0x63, 0x00, 0x74,
0x03, 0xda, 0xb3, 0xa0, 0x24, 0xbc, 0x1a, 0x3a, 0x01, 0xda, 0x0e, 0x1a,
0x85, 0xbc, 0x45, 0xa3, 0x57, 0xa0, 0xff, 0x80, 0x32, 0x20, 0x4e, 0x42,
0x5b, 0xa0, 0x46, 0x02, 0x46, 0xa0, 0x3d, 0xea, 0xab, 0x81, 0xee, 0x86,
0xb2, 0xab, 0x4e, 0x29, 0xaa, 0xb4, 0x8d, 0x36, 0xd2, 0x56, 0xda, 0x6c,
0xa4, 0x8d, 0x08, 0x7c, 0x84, 0xf3, 0x7f, 0x03, 0x7d, 0x00, 0x55, 0x95,
0x74, 0x37, 0xbb, 0x68, 0x33, 0x6d, 0xa7, 0x0f, 0x46, 0x8a, 0x44, 0xe0,
0x63, 0x94, 0x3f, 0x0d, 0x75, 0x03, 0x37, 0x6e, 0xe9, 0xf4, 0x85, 0x3e,
0x19, 0x29, 0x80, 0x00, 0xbb, 0xcc, 0x9f, 0x40, 0xf9, 0x9b, 0x1a, 0x37,
0x82, 0xfd, 0xda, 0x4b, 0xdf, 0xe8, 0xa3, 0xf9, 0x79, 0x00, 0x08, 0xb6,
0xf0, 0x02, 0xee, 0x73, 0xe8, 0xb7, 0x50, 0xbf, 0x40, 0xc6, 0xbd, 0x1c,
0x7d, 0xa5, 0xcf, 0x51, 0x5d, 0xbc, 0xa2, 0x69, 0x35, 0x84, 0xb7, 0x6d,
0xe7, 0xa0, 0x71, 0x27, 0xb4, 0x54, 0xfb, 0xe9, 0x3b, 0x31, 0x48, 0x9c,
0x70, 0xa0, 0x66, 0x17, 0x94, 0xb7, 0x51, 0xa5, 0x82, 0xa7, 0xcb, 0x79,
0xc4, 0x80, 0x58, 0x10, 0x13, 0xed, 0x85, 0xe3, 0x0e, 0xab, 0xa0, 0xdf,
0x41, 0x75, 0x21, 0x30, 0x28, 0x3f, 0x88, 0x09, 0xb1, 0x89, 0xe3, 0xd8,
0x0c, 0xcc, 0xf6, 0x16, 0x46, 0xf8, 0x57, 0xd0, 0xa0, 0x00, 0xd3, 0xb5,
0x1e, 0x62, 0xa4, 0x5d, 0x6f, 0x50, 0x0d, 0xa7, 0xea, 0x0c, 0xf9, 0xbe,
0x83, 0x9f, 0x58, 0x11, 0xb3, 0xd8, 0x0b, 0x6f, 0x77, 0xbe, 0x80, 0xbe,
0x81, 0xea, 0xfa, 0x8d, 0x95, 0xe5, 0x17, 0x31, 0x23, 0x76, 0xb1, 0xbd,
0x65, 0xe4, 0x84, 0x4c, 0xad, 0x21, 0xbe, 0xcd, 0x81, 0x4f, 0x0c, 0x89,
0x65, 0xac, 0xe4, 0x7d, 0x58, 0x7b, 0x03, 0x2a, 0xeb, 0xdb, 0x91, 0xb4,
0x7a, 0x89, 0x25, 0x31, 0x8d, 0x85, 0x8c, 0x87, 0x95, 0xf7, 0xa0, 0x49,
0x23, 0x49, 0xb6, 0xbf, 0xc4, 0x94, 0xd8, 0x2a, 0x2d, 0xd5, 0xb0, 0xae,
0x09, 0x2a, 0x1b, 0x8c, 0xa4, 0xd6, 0x4f, 0x6c, 0x89, 0xb1, 0x92, 0xf2,
0x09, 0xac, 0xfa, 0x1e, 0x9a, 0x54, 0x72, 0xc2, 0xf2, 0x9b, 0x18, 0x13,
0x6b, 0xa5, 0xa4, 0x1a, 0xd6, 0x18, 0xf2, 0xc3, 0x0b, 0x7e, 0x62, 0x4d,
0xcc, 0x95, 0x90, 0x49, 0xb0, 0xe2, 0x09, 0x34, 0xac, 0x6f, 0x80, 0x69,
0xe7, 0x1d, 0xd6, 0xc4, 0x9c, 0xd8, 0x47, 0x2a, 0x5c, 0x78, 0x59, 0x0f,
0x35, 0xa4, 0x44, 0x83, 0x01, 0xb1, 0x27, 0x07, 0x91, 0xc8, 0x60, 0xb4,
0x7a, 0x17, 0x6a, 0xc8, 0x8f, 0x16, 0x03, 0x72, 0x40, 0x2e, 0x42, 0x95,
0x4e, 0x68, 0x2d, 0xc9, 0xd3, 0xb8, 0xaa, 0x05, 0x3d, 0xb9, 0x20, 0x27,
0x45, 0x4b, 0xa9, 0xc3, 0x8c, 0xdb, 0xd1, 0xd2, 0x8f, 0x8b, 0x6e, 0xcd,
0x9c, 0x20, 0x0b, 0x81, 0x41, 0xa8, 0xf8, 0x07, 0xd0, 0xc3, 0xb2, 0x1a,
0xc8, 0xae, 0xf7, 0x53, 0x1c, 0xa8, 0xf6, 0x0d, 0x30, 0xf6, 0xbc, 0xe3,
0x84, 0xdc, 0x48, 0x15, 0x5e, 0x70, 0x98, 0x2b, 0x7e, 0x75, 0xbf, 0x00,
0xe4, 0xa6, 0xa8, 0x8b, 0xc2, 0x62, 0xd6, 0xa4, 0xbd, 0x87, 0xca, 0xff,
0x06, 0x2d, 0x87, 0x1a, 0x51, 0x13, 0x01, 0x72, 0x43, 0x8e, 0xc8, 0x95,
0x2f, 0x29, 0x26, 0x00, 0x7e, 0x85, 0x1a, 0x95, 0x1f, 0x8b, 0xf6, 0xe3,
0xf5, 0x80, 0x01, 0x03, 0xc4, 0xee, 0xdd, 0xbb, 0x45, 0x5d, 0x5d, 0x9d,
0xa5, 0xdc, 0x67, 0x9a, 0x26, 0x42, 0x8e, 0xc8, 0x55, 0xa0, 0xf2, 0x01,
0x6a, 0x7b, 0x01, 0x8d, 0xfd, 0x6f, 0x6d, 0xbf, 0x7e, 0xfd, 0x52, 0x0f,
0x1f, 0x3e, 0x4c, 0xe5, 0x0b, 0xd3, 0x98, 0xa7, 0x83, 0x8f, 0x69, 0xae,
0xc8, 0x59, 0x60, 0x52, 0x8b, 0x9a, 0xb4, 0x00, 0x67, 0xd7, 0xae, 0x5d,
0xf9, 0xdc, 0x67, 0x8e, 0x99, 0xa7, 0x8b, 0x9f, 0xf0, 0x83, 0x9c, 0x05,
0x22, 0x9f, 0xa1, 0x16, 0x6d, 0x80, 0xb9, 0x7d, 0xfb, 0x76, 0x86, 0xf0,
0xfc, 0x1d, 0xe6, 0xe9, 0xe4, 0x2b, 0x7c, 0x21, 0x77, 0x05, 0xa5, 0xac,
0x60, 0xee, 0xbb, 0x95, 0x28, 0x7c, 0x1c, 0xba, 0x9f, 0x47, 0xb9, 0xd8,
0x64, 0xbf, 0x78, 0xf1, 0x42, 0x74, 0xea, 0xe4, 0x3c, 0x66, 0xf2, 0xf2,
0xe5, 0x4b, 0xd1, 0xb9, 0x73, 0xe7, 0xd8, 0xf8, 0xe2, 0xc3, 0xd0, 0xff,
0xa2, 0x4c, 0x05, 0xd4, 0xf5, 0x11, 0x76, 0xaf, 0x8b, 0xc0, 0x8d, 0x38,
0x59, 0x1b, 0xf2, 0x7d, 0x00, 0xa6, 0x5b, 0x11, 0x72, 0x47, 0x0e, 0x5d,
0xa5, 0x50, 0x0f, 0xc0, 0x91, 0x25, 0x8e, 0x33, 0xf3, 0x4d, 0x1a, 0xda,
0x48, 0xc2, 0x7a, 0x00, 0xf2, 0xf6, 0x14, 0x3a, 0x14, 0xfa, 0x3f, 0x1e,
0xe4, 0x4b, 0xa1, 0x1e, 0xe0, 0x97, 0x28, 0xac, 0x15, 0xf9, 0xf9, 0xce,
0x27, 0xe4, 0x98, 0x1c, 0x92, 0x4b, 0x47, 0x71, 0xeb, 0x01, 0x7a, 0xa1,
0xf4, 0x7f, 0xa0, 0x3d, 0x1d, 0xcf, 0x8a, 0x71, 0x62, 0x02, 0x7b, 0x00,
0xb2, 0xf5, 0x18, 0x3a, 0x0c, 0xca, 0x25, 0x65, 0x39, 0xe2, 0xd6, 0x03,
0xfc, 0x02, 0xa5, 0xb4, 0x23, 0x3f, 0xc7, 0xf3, 0x64, 0x1d, 0x90, 0x4b,
0x72, 0xda, 0x4a, 0x9c, 0x7a, 0x80, 0x2e, 0x28, 0xc5, 0x15, 0xa8, 0xbc,
0x06, 0xd0, 0x4e, 0x12, 0xda, 0x03, 0x90, 0x47, 0x5e, 0x03, 0x70, 0xdd,
0xc0, 0x73, 0x1e, 0xd8, 0xe2, 0xd4, 0x03, 0x70, 0x9a, 0x57, 0x4b, 0xf2,
0x6d, 0xa7, 0x13, 0xba, 0x25, 0xa7, 0xad, 0xa6, 0xf0, 0x3b, 0x38, 0x80,
0xf1, 0x73, 0x87, 0xb4, 0xc8, 0x93, 0x78, 0x7f, 0x3e, 0x67, 0xce, 0x1c,
0x31, 0x7d, 0xfa, 0x74, 0x31, 0x68, 0xd0, 0x20, 0xd1, 0xbb, 0x77, 0xef,
0x92, 0x6c, 0xea, 0xd8, 0xb1, 0xa3, 0xeb, 0x79, 0xcc, 0x3b, 0x74, 0xe8,
0x90, 0x6b, 0x7e, 0xa1, 0x8c, 0xc6, 0xc6, 0x46, 0x6b, 0x5e, 0xe1, 0xf4,
0xe9, 0xd3, 0xe2, 0xf8, 0xf1, 0xe3, 0x82, 0x3d, 0x8d, 0x82, 0xf2, 0x33,
0xd8, 0xf4, 0x97, 0x42, 0x76, 0xfd, 0x10, 0x99, 0x4a, 0x3d, 0xc7, 0xd7,
0xa5, 0x4b, 0x97, 0xd4, 0xa6, 0x4d, 0x9b, 0x52, 0x4d, 0x4d, 0x4d, 0xf9,
0x03, 0x77, 0xca, 0x1e, 0xd3, 0x56, 0xda, 0x4c, 0xdb, 0x81, 0xa7, 0x4a,
0x4a, 0x6e, 0xc9, 0xb1, 0xab, 0x6c, 0x40, 0x8e, 0x32, 0x06, 0x8f, 0x18,
0x31, 0x22, 0x75, 0xe3, 0xc6, 0x0d, 0x65, 0x89, 0xf6, 0x32, 0x8c, 0xb6,
0xd3, 0x07, 0x95, 0x30, 0x85, 0x2d, 0xe4, 0xd8, 0x55, 0x94, 0x79, 0x9e,
0x6f, 0xd4, 0xa8, 0x51, 0xa9, 0xfa, 0xfa, 0x7a, 0x2f, 0x8c, 0x95, 0xcf,
0xa7, 0x0f, 0xf4, 0x05, 0x88, 0xab, 0xa2, 0xe4, 0xd8, 0x51, 0x3e, 0x44,
0xaa, 0x12, 0x46, 0x76, 0xed, 0xda, 0x35, 0x75, 0xed, 0xda, 0x35, 0xe5,
0xc9, 0xf5, 0x6b, 0x20, 0x7d, 0xa1, 0x4f, 0xaa, 0xe0, 0x0b, 0x3b, 0xc8,
0xb5, 0x25, 0xd9, 0x77, 0x01, 0x3f, 0xb2, 0x13, 0xa3, 0xde, 0xae, 0x5d,
0xbb, 0x56, 0x8c, 0x1b, 0x37, 0x2e, 0x6a, 0x33, 0x02, 0x6b, 0x9f, 0xbe,
0xd0, 0x27, 0x85, 0xc4, 0x91, 0x6b, 0xbe, 0x9a, 0x24, 0xf2, 0x28, 0x2d,
0x2f, 0x2f, 0x8f, 0xd5, 0x05, 0x9f, 0xdf, 0x5e, 0x80, 0x17, 0x86, 0xf4,
0x4d, 0x05, 0x8c, 0x61, 0xc3, 0x97, 0x76, 0x30, 0xda, 0x3d, 0x00, 0x6f,
0x07, 0x67, 0xd8, 0x89, 0x51, 0x6e, 0x6b, 0x6a, 0x6a, 0x44, 0xcf, 0x9e,
0xfa, 0x0d, 0x42, 0xd2, 0x27, 0xfa, 0xa6, 0x88, 0x54, 0xc1, 0x0e, 0x6b,
0x08, 0xc0, 0x0e, 0x80, 0x4a, 0x24, 0x28, 0x31, 0xf1, 0x33, 0x77, 0xee,
0x5c, 0x45, 0x30, 0x0a, 0xde, 0x0c, 0x85, 0x7c, 0x23, 0xd7, 0xe4, 0x3c,
0xf3, 0xa6, 0xca, 0xd9, 0xc1, 0xbb, 0x5b, 0x5a, 0x8d, 0x43, 0x87, 0x0e,
0x2d, 0xed, 0xc4, 0x18, 0x9c, 0xa5, 0x98, 0x6f, 0x16, 0xe7, 0x76, 0x0f,
0x30, 0x5d, 0x15, 0xfc, 0xfa, 0xf7, 0xef, 0xaf, 0x8a, 0x29, 0x81, 0xdb,
0xa1, 0x98, 0x6f, 0x16, 0xe7, 0x76, 0x00, 0x8c, 0x09, 0xdc, 0xdb, 0x12,
0x2b, 0xec, 0xd0, 0xc1, 0xfa, 0x69, 0x2a, 0xf1, 0x6c, 0xb5, 0x4f, 0x53,
0xcc, 0x37, 0x8b, 0x73, 0x06, 0x40, 0x37, 0x28, 0x67, 0x89, 0x8c, 0x24,
0x0b, 0x01, 0x72, 0xde, 0x8d, 0x01, 0xc0, 0x45, 0x83, 0x4e, 0xd3, 0xc2,
0xc9, 0x82, 0x23, 0x79, 0xde, 0x92, 0xf3, 0x0a, 0x3b, 0x00, 0x92, 0xe7,
0xbe, 0xf1, 0x98, 0x08, 0x58, 0x01, 0x10, 0xe8, 0x13, 0x24, 0x51, 0xe0,
0x7a, 0xe2, 0xc4, 0x09, 0xb1, 0x62, 0xc5, 0x0a, 0x31, 0x71, 0xe2, 0x44,
0x31, 0x73, 0xe6, 0x4c, 0xb1, 0x7e, 0xfd, 0x7a, 0x71, 0xff, 0xfe, 0xfd,
0xc0, 0x4c, 0x39, 0x7b, 0xf6, 0xac, 0x58, 0xb9, 0x72, 0xa5, 0x98, 0x30,
0x61, 0x82, 0x98, 0x3f, 0x7f, 0xbe, 0xd8, 0xb6, 0x6d, 0x9b, 0x78, 0xf5,
0xea, 0x55, 0x60, 0xf5, 0x47, 0x58, 0x91, 0xc5, 0xfd, 0x9f, 0x61, 0x40,
0x28, 0x23, 0x54, 0x3d, 0x7a, 0xf4, 0xb0, 0x66, 0xc7, 0x38, 0x43, 0x46,
0x1d, 0x3c, 0x78, 0x70, 0xab, 0x76, 0x8b, 0x99, 0x03, 0x78, 0xfb, 0xf6,
0x6d, 0x6a, 0xf5, 0xea, 0xd5, 0xad, 0xea, 0xa0, 0x3f, 0x58, 0x2f, 0x90,
0x3a, 0x78, 0xf0, 0xa0, 0xdf, 0x81, 0x3a, 0xd7, 0x72, 0x3b, 0x76, 0xec,
0x48, 0x61, 0x9d, 0x40, 0xab, 0x36, 0x2a, 0x2b, 0x2b, 0x8b, 0x9e, 0xac,
0xa2, 0x6f, 0xf9, 0x58, 0x13, 0x03, 0x1b, 0x0f, 0x6e, 0x89, 0x51, 0x7e,
0x19, 0x89, 0xc7, 0xe4, 0x5e, 0x1c, 0x91, 0xd8, 0x80, 0xe5, 0xcc, 0xe4,
0xc9, 0x93, 0x53, 0x17, 0x2e, 0x5c, 0x48, 0x91, 0xb0, 0x6c, 0x71, 0x02,
0xa4, 0x98, 0x00, 0xd8, 0xbe, 0x7d, 0x7b, 0x41, 0xb0, 0xba, 0x77, 0xef,
0x9e, 0xba, 0x73, 0xe7, 0x4e, 0x76, 0x93, 0x45, 0xed, 0x5f, 0xb9, 0x72,
0x25, 0x85, 0x2b, 0x77, 0xd7, 0x36, 0x96, 0x2d, 0x5b, 0x56, 0x54, 0x7d,
0x7e, 0xfc, 0x25, 0x46, 0xc4, 0x8a, 0x98, 0xc9, 0xe6, 0x25, 0xcd, 0xbd,
0x38, 0x25, 0xb3, 0xa1, 0xb1, 0x63, 0xc7, 0xa6, 0xb0, 0x3a, 0xc6, 0x11,
0x28, 0x3f, 0x80, 0x38, 0x9e, 0x88, 0xc4, 0xe7, 0xcf, 0x9f, 0xfb, 0x9a,
0x61, 0x5b, 0xba, 0x74, 0xa9, 0x5b, 0x15, 0x9e, 0xe9, 0x4b, 0x96, 0x2c,
0xf1, 0x24, 0xe1, 0xe6, 0xcd, 0x9b, 0x9e, 0xf5, 0xd8, 0x05, 0x8a, 0xf1,
0x97, 0x98, 0x11, 0x3b, 0x99, 0xdc, 0xa0, 0xee, 0x53, 0xbc, 0x08, 0xec,
0x01, 0x95, 0x26, 0x5b, 0xb6, 0x6c, 0x71, 0x7d, 0x14, 0xab, 0x2d, 0x8d,
0xe2, 0xdb, 0x29, 0x9e, 0x3d, 0x7b, 0xe6, 0x59, 0xc5, 0x99, 0x33, 0x67,
0x3c, 0xcb, 0xb8, 0x15, 0x38, 0x77, 0x8e, 0xaf, 0xde, 0x29, 0x2c, 0xe7,
0xcf, 0x9f, 0x2f, 0x5c, 0xa0, 0xc4, 0x5c, 0x3e, 0xbe, 0x46, 0xec, 0x24,
0x4b, 0x0f, 0x06, 0x80, 0xd4, 0x17, 0x3e, 0x4c, 0x9d, 0x3a, 0x55, 0x8a,
0x0f, 0x77, 0xef, 0xde, 0xf5, 0x55, 0xef, 0xbd, 0x7b, 0xf7, 0x04, 0xba,
0x55, 0x5f, 0x65, 0xb3, 0x0b, 0xf1, 0x9c, 0x07, 0x0f, 0x1e, 0x64, 0x27,
0x39, 0xee, 0xb3, 0x7e, 0x59, 0x22, 0x0b, 0xbb, 0x2c, 0x7b, 0xcb, 0xa5,
0x07, 0x80, 0xac, 0xe1, 0xcf, 0xf1, 0xe3, 0xfd, 0xbd, 0xab, 0x02, 0xdd,
0xa8, 0x68, 0xd7, 0x8e, 0x6e, 0x16, 0x27, 0x3c, 0x67, 0xf4, 0xe8, 0xd1,
0x9e, 0x27, 0x8d, 0x19, 0x23, 0x6f, 0x10, 0x55, 0x16, 0x76, 0x59, 0x4e,
0xc9, 0x0f, 0x80, 0xac, 0xc6, 0x02, 0xdd, 0xad, 0xa8, 0xa8, 0x10, 0xc3,
0x87, 0x0f, 0xf7, 0xac, 0x73, 0xde, 0xbc, 0x79, 0x9e, 0x65, 0xdc, 0x0a,
0xcc, 0x9a, 0x35, 0xcb, 0x2d, 0xcb, 0x4a, 0xc7, 0xa2, 0x4f, 0x11, 0xc2,
0xb7, 0xb4, 0xa0, 0x0d, 0x6d, 0xcc, 0xb4, 0x7a, 0x7f, 0xa9, 0x6f, 0xfe,
0xb0, 0x2f, 0x80, 0x9c, 0xb6, 0xc5, 0x5c, 0x14, 0x39, 0x9d, 0x7f, 0xf2,
0xe4, 0xc9, 0x54, 0x59, 0x59, 0x99, 0xeb, 0x85, 0x12, 0xd7, 0xe2, 0xb5,
0xb4, 0xb4, 0x38, 0x9d, 0xea, 0x2b, 0xad, 0xa1, 0xa1, 0x21, 0xd5, 0xb7,
0x6f, 0x5f, 0xd7, 0xfa, 0x37, 0x6f, 0xde, 0xec, 0xab, 0x1e, 0xbb, 0x50,
0x29, 0xfe, 0x82, 0x60, 0xd7, 0xf6, 0x03, 0xc8, 0x7b, 0xc1, 0xbe, 0xb1,
0xb9, 0x8d, 0x51, 0x14, 0xd9, 0xe9, 0x1c, 0xf4, 0xd9, 0xbf, 0x7f, 0xbf,
0xc0, 0xab, 0x5d, 0x5a, 0xd9, 0x30, 0x63, 0xc6, 0x0c, 0x71, 0xec, 0xd8,
0x31, 0x81, 0xb5, 0x78, 0xad, 0xf2, 0xfc, 0x26, 0xf4, 0xe9, 0xd3, 0x47,
0x1c, 0x3d, 0x7a, 0x54, 0x8c, 0x1c, 0x39, 0x32, 0xe7, 0x14, 0x04, 0x9d,
0xd8, 0xb0, 0x61, 0x83, 0x58, 0xb7, 0x6e, 0x5d, 0x4e, 0x7a, 0x0c, 0x0f,
0x9a, 0x39, 0xf5, 0xc6, 0x00, 0xe8, 0x13, 0x43, 0xe3, 0x2d, 0x93, 0x17,
0x2f, 0x5e, 0x2c, 0xaa, 0xaa, 0xaa, 0xac, 0x87, 0x31, 0x2e, 0x5e, 0xbc,
0x28, 0x7a, 0xf5, 0xea, 0x25, 0x70, 0x0f, 0x2d, 0x66, 0xcf, 0x9e, 0x2d,
0x48, 0x54, 0x5b, 0x65, 0xd2, 0xa4, 0x49, 0xe2, 0xd2, 0xa5, 0x4b, 0xe2,
0xc8, 0x91, 0x23, 0x82, 0x57, 0xfc, 0x03, 0x07, 0x0e, 0x14, 0xfc, 0x69,
0xe0, 0xa8, 0xa0, 0x06, 0x62, 0x7d, 0xf9, 0xaf, 0xc2, 0x11, 0x69, 0xdd,
0x8c, 0xdd, 0xfd, 0x39, 0x6d, 0x4b, 0xe9, 0x12, 0x9d, 0xea, 0x89, 0x4b,
0x5a, 0x29, 0xfe, 0xca, 0xe4, 0x06, 0x75, 0x5f, 0xe5, 0x4f, 0xc0, 0x13,
0xa8, 0x91, 0x64, 0x22, 0xf0, 0x24, 0xd6, 0xd7, 0x00, 0xc9, 0xe4, 0x2c,
0x50, 0xaf, 0x9b, 0x19, 0x00, 0x8d, 0x81, 0x56, 0x69, 0x2a, 0x8b, 0x13,
0x02, 0x8d, 0x0c, 0x80, 0xdb, 0x71, 0xb2, 0xd8, 0xd8, 0x1a, 0x28, 0x02,
0xb7, 0x19, 0x00, 0xdf, 0x04, 0x5a, 0xa5, 0xa9, 0x2c, 0x4e, 0x08, 0x7c,
0xc3, 0x00, 0xe0, 0x7b, 0x00, 0x8d, 0x24, 0x13, 0x81, 0x4c, 0x00, 0xf0,
0x36, 0xd0, 0x48, 0xb2, 0x10, 0x20, 0xe7, 0xb7, 0xd8, 0x03, 0xb4, 0x40,
0xe5, 0x4d, 0x69, 0x25, 0x0b, 0xd4, 0x38, 0x79, 0x4b, 0xce, 0x5b, 0x18,
0x00, 0x94, 0x9b, 0xef, 0x36, 0xe6, 0x33, 0x41, 0x08, 0x58, 0x9c, 0x73,
0x28, 0x98, 0x72, 0x0a, 0x3a, 0xd7, 0xda, 0x8b, 0xd9, 0xc7, 0x9b, 0x37,
0x6f, 0x04, 0x46, 0x02, 0x5d, 0xad, 0xce, 0x7f, 0x18, 0x83, 0xf3, 0xfc,
0x85, 0xd6, 0x07, 0xb4, 0x6f, 0xdf, 0x3e, 0x67, 0x08, 0xf9, 0xf5, 0xeb,
0xd7, 0xae, 0x75, 0x73, 0xa8, 0x99, 0xe5, 0x63, 0x2a, 0xe4, 0x3c, 0xf3,
0x6c, 0xe0, 0xdf, 0x65, 0x39, 0xd1, 0xdc, 0x2c, 0x77, 0xae, 0x69, 0xf9,
0xf2, 0xe5, 0x82, 0x2f, 0x77, 0x72, 0xd3, 0xeb, 0xd7, 0xaf, 0xe7, 0xb8,
0xb6, 0x75, 0xeb, 0x56, 0xd7, 0xb2, 0xac, 0x63, 0xdf, 0xbe, 0x7d, 0x99,
0xf2, 0x3c, 0xd7, 0xad, 0x5e, 0xa6, 0xb3, 0x6d, 0x99, 0x22, 0x19, 0x3b,
0x8b, 0x73, 0xfb, 0x27, 0xe0, 0x2c, 0x1c, 0x91, 0xc2, 0xd4, 0xe5, 0xcb,
0x97, 0x65, 0x62, 0xa4, 0x75, 0xdd, 0x12, 0xb1, 0x23, 0xd7, 0xe4, 0x3c,
0xd3, 0x03, 0xb0, 0x9f, 0xfb, 0x27, 0x13, 0x82, 0x16, 0xcc, 0x99, 0x07,
0x5d, 0xa5, 0xd4, 0xfa, 0x0a, 0xfd, 0x9c, 0x48, 0x6d, 0xd8, 0xa1, 0x72,
0x89, 0xd8, 0x91, 0x6b, 0xeb, 0xb7, 0xcd, 0xee, 0x01, 0xd8, 0xbc, 0x94,
0x9f, 0x01, 0xce, 0xa7, 0xaf, 0x5a, 0xb5, 0xca, 0xd7, 0x02, 0x4e, 0x07,
0x0c, 0x42, 0x4f, 0x0a, 0x62, 0x0a, 0xb9, 0xad, 0x46, 0x73, 0xb1, 0x2b,
0x31, 0x23, 0x76, 0x92, 0x24, 0xc3, 0xb5, 0x7d, 0x11, 0xc8, 0x76, 0x32,
0x89, 0x41, 0x37, 0xba, 0x73, 0xe7, 0x4e, 0xb1, 0x77, 0xef, 0x5e, 0x6b,
0x9e, 0x3e, 0x7b, 0x9d, 0xdb, 0xe3, 0xc7, 0x7c, 0x87, 0x71, 0xdb, 0x64,
0xcd, 0x9a, 0x35, 0x62, 0xe1, 0xc2, 0x85, 0xae, 0x95, 0x0c, 0x19, 0x32,
0x24, 0x27, 0x6f, 0xd1, 0xa2, 0x45, 0x62, 0xd8, 0xb0, 0x61, 0x39, 0x69,
0xd9, 0x07, 0xd3, 0xa6, 0x4d, 0xcb, 0x1c, 0xf2, 0xdc, 0x3d, 0x7b, 0xf6,
0x64, 0x8e, 0xf3, 0x77, 0x82, 0x78, 0xde, 0x7f, 0xe3, 0xc6, 0x8d, 0x39,
0x6f, 0x44, 0xc1, 0x5b, 0xc5, 0x04, 0x57, 0x23, 0x07, 0x81, 0x4d, 0xbe,
0xbd, 0x59, 0xc7, 0xae, 0x5c, 0xdf, 0x40, 0x21, 0x69, 0x6b, 0x03, 0xfc,
0xd4, 0xcd, 0x39, 0x73, 0x5d, 0xc5, 0x69, 0x3d, 0x80, 0x1f, 0x4c, 0x02,
0x2e, 0x43, 0x8e, 0x33, 0x92, 0xfd, 0x13, 0xc0, 0xc4, 0x3f, 0x66, 0x72,
0xcc, 0x8e, 0xae, 0x08, 0xe4, 0x70, 0x9c, 0x1f, 0x00, 0x7f, 0x82, 0xd7,
0xc5, 0x2f, 0xa2, 0xd7, 0x15, 0x2a, 0xfd, 0xfc, 0x22, 0xb7, 0xe4, 0x38,
0x23, 0xf9, 0x01, 0x50, 0x87, 0x9c, 0xe3, 0x99, 0xdc, 0x08, 0x76, 0x0a,
0x0d, 0xbc, 0x44, 0x60, 0x4e, 0xa0, 0x4d, 0x2a, 0xf0, 0x44, 0x31, 0xb9,
0x25, 0xc7, 0x19, 0xc9, 0x0f, 0x00, 0x66, 0xe4, 0x74, 0x11, 0x99, 0x92,
0x21, 0xed, 0xe0, 0x0f, 0x1c, 0x43, 0x6a, 0x29, 0xfc, 0x66, 0x1e, 0x3d,
0x7a, 0x14, 0x7e, 0xa3, 0xb9, 0x2d, 0xb6, 0xe2, 0xd6, 0x29, 0x00, 0x0e,
0xe2, 0x1c, 0xfe, 0xb9, 0x40, 0x24, 0x22, 0x71, 0xf0, 0x23, 0x12, 0x7f,
0xb2, 0x1b, 0x8d, 0xd8, 0x37, 0x72, 0x4a, 0x6e, 0x73, 0xc4, 0x29, 0x00,
0x9e, 0xa3, 0xc4, 0xef, 0x72, 0x4a, 0x85, 0x78, 0x70, 0xe0, 0xc0, 0x81,
0x10, 0x5b, 0x0b, 0xb7, 0xa9, 0x88, 0x7d, 0x23, 0xa7, 0xe4, 0xd6, 0x97,
0xf4, 0x42, 0xa9, 0x26, 0x68, 0x24, 0xb7, 0x84, 0x18, 0x00, 0xd1, 0xee,
0x4e, 0x90, 0x3e, 0x45, 0x85, 0x67, 0x9a, 0x4b, 0x72, 0x5a, 0x94, 0xfc,
0x1a, 0xa5, 0x23, 0x31, 0x1a, 0x03, 0x35, 0x29, 0x3e, 0x96, 0xa5, 0x8b,
0xd0, 0x17, 0xfa, 0x14, 0x15, 0x9e, 0x68, 0x97, 0x5c, 0x16, 0x2d, 0xfc,
0x8f, 0x19, 0x4e, 0x1a, 0x44, 0x62, 0xf8, 0x94, 0x29, 0x53, 0x8a, 0x7e,
0x05, 0x8b, 0x8a, 0x01, 0xc3, 0xff, 0x0b, 0xa0, 0x2f, 0x51, 0xe1, 0x98,
0xe6, 0x90, 0x5c, 0x96, 0x24, 0x5b, 0x71, 0x56, 0x64, 0xc6, 0x63, 0x28,
0x36, 0x85, 0x21, 0xe4, 0x56, 0xaf, 0x96, 0x51, 0x91, 0xe8, 0x7c, 0x9b,
0xb0, 0xe6, 0xc0, 0xb2, 0x9d, 0x3e, 0x44, 0x89, 0x21, 0xda, 0x26, 0x87,
0xae, 0x52, 0xe6, 0x9a, 0xf3, 0x2e, 0x83, 0xff, 0xcc, 0xc4, 0x45, 0xa3,
0xad, 0x9f, 0xbe, 0xf4, 0x38, 0x31, 0xc8, 0x6c, 0xbc, 0x48, 0x49, 0x2c,
0x58, 0xb0, 0x40, 0xe0, 0x25, 0x4a, 0x25, 0xff, 0x59, 0x54, 0x90, 0xf6,
0x14, 0xaa, 0x8b, 0x7f, 0x1e, 0x85, 0xf7, 0x12, 0x89, 0xc3, 0x87, 0x0f,
0x0b, 0x99, 0x2f, 0x8f, 0x28, 0x64, 0x43, 0x56, 0x9e, 0xe7, 0x9f, 0x47,
0x67, 0x95, 0x75, 0xdd, 0xfd, 0x0c, 0x39, 0x51, 0x47, 0xb1, 0x69, 0xbf,
0x34, 0x0e, 0x96, 0xbb, 0xb2, 0x5a, 0x64, 0x46, 0xad, 0x09, 0x82, 0xd8,
0x7d, 0x09, 0xc8, 0x99, 0xa7, 0x78, 0xfd, 0x04, 0xd8, 0x15, 0x7c, 0x80,
0x9d, 0x2b, 0xd0, 0x4e, 0x76, 0x82, 0xd9, 0x2a, 0x8d, 0xc0, 0x4b, 0x58,
0xc7, 0xe7, 0xd7, 0x3d, 0x1f, 0xfa, 0xf1, 0xbb, 0xa2, 0xb1, 0x01, 0x95,
0xbd, 0x07, 0x9d, 0x01, 0x35, 0xa2, 0x3e, 0x02, 0xbc, 0xf0, 0xfb, 0x6b,
0xd0, 0x66, 0x32, 0x00, 0xa4, 0xbe, 0x4b, 0x00, 0xf5, 0x9b, 0xdf, 0xfa,
0xb6, 0x63, 0x40, 0x8e, 0xc8, 0x95, 0x14, 0xe1, 0x6b, 0xb3, 0x9e, 0x40,
0x0d, 0x51, 0x6a, 0x62, 0x40, 0x6e, 0xc8, 0x91, 0x54, 0xf9, 0x14, 0xb5,
0x9b, 0x00, 0x50, 0x13, 0x03, 0x72, 0x13, 0x8a, 0xfc, 0x1e, 0xad, 0x98,
0x20, 0x50, 0x0b, 0x03, 0x72, 0x12, 0x9a, 0xf0, 0x6e, 0x80, 0xef, 0x51,
0x35, 0x41, 0xa0, 0x06, 0x06, 0xe4, 0x22, 0xf4, 0x3b, 0x34, 0xfe, 0xe5,
0xc8, 0x5d, 0x13, 0x04, 0x91, 0x7f, 0x09, 0xc8, 0x01, 0xb9, 0x88, 0x44,
0x78, 0xc1, 0x51, 0x0f, 0x35, 0x3d, 0x41, 0x34, 0x18, 0x10, 0x7b, 0xe9,
0x17, 0x7d, 0x5e, 0x91, 0x35, 0x09, 0x05, 0xcc, 0x9d, 0x41, 0xf8, 0x01,
0x40, 0xcc, 0x89, 0xbd, 0x12, 0x52, 0x0d, 0x2b, 0xbe, 0x87, 0x9a, 0x9e,
0x20, 0x1c, 0x0c, 0x88, 0x35, 0x31, 0x57, 0x4a, 0x3e, 0x81, 0x35, 0x26,
0x08, 0xe4, 0x07, 0x00, 0x31, 0x26, 0xd6, 0x4a, 0x4a, 0x35, 0xac, 0x8a,
0x6c, 0x29, 0x19, 0xda, 0xd6, 0xbd, 0x07, 0x22, 0xb6, 0xc4, 0x58, 0x69,
0xe1, 0x8b, 0xfc, 0xef, 0x41, 0x75, 0x27, 0x23, 0x6c, 0xff, 0x88, 0xa9,
0xbf, 0x3f, 0x49, 0x50, 0x20, 0x3c, 0xde, 0x87, 0x0d, 0x91, 0x3f, 0x67,
0xa8, 0x51, 0x10, 0x12, 0x4b, 0x62, 0x1a, 0x2b, 0xe1, 0x6a, 0xa2, 0x5a,
0x68, 0xd8, 0xdf, 0x14, 0xdd, 0xda, 0x23, 0x86, 0xc4, 0x32, 0x96, 0xc2,
0xe9, 0xe6, 0x2f, 0xa0, 0x6f, 0xa0, 0xba, 0x11, 0x23, 0xdb, 0x1f, 0x62,
0x46, 0xec, 0xfc, 0x4e, 0xd9, 0xa3, 0xa8, 0xba, 0x52, 0x0d, 0xd3, 0xf8,
0x4c, 0x9a, 0x6c, 0xd0, 0x74, 0xa9, 0x9f, 0x58, 0x11, 0x33, 0xad, 0xa4,
0x2f, 0xbc, 0xf9, 0x0a, 0xaa, 0x0b, 0x49, 0xb2, 0xfc, 0x20, 0x46, 0xc4,
0x4a, 0x4b, 0x29, 0x83, 0x57, 0xab, 0xa0, 0xdf, 0x41, 0x65, 0x01, 0x18,
0xd7, 0x7a, 0x89, 0x09, 0xb1, 0x21, 0x46, 0xda, 0x0b, 0x23, 0xfc, 0x0f,
0xd0, 0xb7, 0xd0, 0xb8, 0x12, 0x16, 0x94, 0xdd, 0xc4, 0x80, 0x58, 0x68,
0xfb, 0xad, 0x87, 0x6f, 0xae, 0x52, 0x89, 0x9c, 0x24, 0x4f, 0x2b, 0xd3,
0x77, 0x62, 0x90, 0x68, 0xe1, 0x13, 0xca, 0x9f, 0x43, 0xbf, 0x85, 0x06,
0xf5, 0xad, 0x52, 0xbd, 0x1e, 0xfa, 0x4a, 0x9f, 0xe9, 0xbb, 0x91, 0x34,
0x02, 0xbc, 0xdd, 0xf9, 0x29, 0x94, 0xcb, 0xcf, 0x55, 0x27, 0xb0, 0x54,
0xfb, 0xe8, 0x1b, 0x7d, 0xd4, 0xe2, 0xd6, 0x0e, 0x7e, 0x48, 0x93, 0x8f,
0x51, 0xf3, 0x69, 0x68, 0xa9, 0x40, 0xab, 0x76, 0x1e, 0x7d, 0xa1, 0x4f,
0x46, 0x8a, 0x44, 0xe0, 0x23, 0x94, 0xff, 0x2d, 0x94, 0xff, 0xe2, 0xac,
0x1a, 0xa9, 0x5e, 0xf6, 0xd0, 0x66, 0xda, 0x4e, 0x1f, 0x8c, 0xb4, 0x11,
0x01, 0x76, 0x99, 0x35, 0xd0, 0xdd, 0xd0, 0xa7, 0x50, 0x2f, 0xf0, 0xa3,
0xca, 0xa7, 0x6d, 0xb4, 0x91, 0xb6, 0xc6, 0xa2, 0x9b, 0x8f, 0xe3, 0x3d,
0x67, 0x37, 0x80, 0x3b, 0x0b, 0x3a, 0x33, 0xad, 0x1f, 0x62, 0x1b, 0xd5,
0xc5, 0x14, 0x6f, 0xe1, 0xae, 0x42, 0x6b, 0xd3, 0x7a, 0x12, 0x5b, 0xfe,
0x01, 0x47, 0x6c, 0x24, 0x8e, 0x01, 0x90, 0x0f, 0x2e, 0x27, 0x4a, 0xaa,
0xa0, 0x0c, 0x08, 0xde, 0x52, 0x55, 0x40, 0x65, 0x4d, 0x9e, 0xf0, 0x2f,
0xf6, 0xf8, 0xb8, 0x3c, 0xdf, 0xb4, 0x4d, 0xd2, 0xbf, 0x86, 0x32, 0x2d,
0xb6, 0xa2, 0x43, 0x00, 0x38, 0x81, 0xcf, 0x41, 0x15, 0x06, 0x02, 0x17,
0x4c, 0x72, 0x3b, 0x02, 0xca, 0x77, 0xe4, 0x74, 0x87, 0x96, 0xa7, 0xd5,
0xde, 0xc7, 0xa1, 0xf5, 0x26, 0x14, 0x76, 0xdf, 0x7c, 0x23, 0x0a, 0x95,
0xfb, 0x5c, 0x7c, 0x71, 0x07, 0x4a, 0xc2, 0x6f, 0xa5, 0xb7, 0x7c, 0x46,
0x52, 0x2b, 0xf9, 0x3f, 0x92, 0xc9, 0x00, 0xb6, 0x61, 0xee, 0xab, 0xc9,
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
}

View File

@ -3,7 +3,9 @@
package model
import "github.com/mattermost/mattermost-server/v5/mlog"
import (
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
type BundleInfo struct {
Path string

View File

@ -34,23 +34,26 @@ const (
)
type Channel struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
TeamId string `json:"team_id"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Header string `json:"header"`
Purpose string `json:"purpose"`
LastPostAt int64 `json:"last_post_at"`
TotalMsgCount int64 `json:"total_msg_count"`
ExtraUpdateAt int64 `json:"extra_update_at"`
CreatorId string `json:"creator_id"`
SchemeId *string `json:"scheme_id"`
Props map[string]interface{} `json:"props" db:"-"`
GroupConstrained *bool `json:"group_constrained"`
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
TeamId string `json:"team_id"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Header string `json:"header"`
Purpose string `json:"purpose"`
LastPostAt int64 `json:"last_post_at"`
TotalMsgCount int64 `json:"total_msg_count"`
ExtraUpdateAt int64 `json:"extra_update_at"`
CreatorId string `json:"creator_id"`
SchemeId *string `json:"scheme_id"`
Props map[string]interface{} `json:"props" db:"-"`
GroupConstrained *bool `json:"group_constrained"`
Shared *bool `json:"shared"`
TotalMsgCountRoot int64 `json:"total_msg_count_root"`
PolicyID *string `json:"policy_id" db:"-"`
}
type ChannelWithTeamData struct {
@ -120,18 +123,21 @@ type ChannelModeratedRolesPatch struct {
// PerPage number of results per page, if paginated.
//
type ChannelSearchOpts struct {
NotAssociatedToGroup string
ExcludeDefaultChannels bool
IncludeDeleted bool
Deleted bool
ExcludeChannelNames []string
TeamIds []string
GroupConstrained bool
ExcludeGroupConstrained bool
Public bool
Private bool
Page *int
PerPage *int
NotAssociatedToGroup string
ExcludeDefaultChannels bool
IncludeDeleted bool
Deleted bool
ExcludeChannelNames []string
TeamIds []string
GroupConstrained bool
ExcludeGroupConstrained bool
PolicyID string
ExcludePolicyConstrained bool
IncludePolicyID bool
Public bool
Private bool
Page *int
PerPage *int
}
type ChannelMemberCountByGroup struct {
@ -140,6 +146,14 @@ type ChannelMemberCountByGroup struct {
ChannelMemberTimezonesCount int64 `db:"-" json:"channel_member_timezones_count"`
}
type ChannelOption func(channel *Channel)
func WithID(ID string) ChannelOption {
return func(channel *Channel) {
channel.Id = ID
}
}
func (o *Channel) DeepCopy() *Channel {
copy := *o
if copy.SchemeId != nil {
@ -313,6 +327,10 @@ func (o *Channel) IsGroupConstrained() bool {
return o.GroupConstrained != nil && *o.GroupConstrained
}
func (o *Channel) IsShared() bool {
return o.Shared != nil && *o.Shared
}
func (o *Channel) GetOtherUserIdForDM(userId string) string {
if o.Type != CHANNEL_DIRECT {
return ""
@ -336,9 +354,8 @@ func (o *Channel) GetOtherUserIdForDM(userId string) string {
func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 {
return userId2 + "__" + userId1
} else {
return userId1 + "__" + userId2
}
return userId1 + "__" + userId2
}
func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string {

View File

@ -14,11 +14,12 @@ import (
type ChannelCounts struct {
Counts map[string]int64 `json:"counts"`
CountsRoot map[string]int64 `json:"counts_root"`
UpdateTimes map[string]int64 `json:"update_times"`
}
func (o *ChannelCounts) Etag() string {
// we don't include CountsRoot in ETag calculation, since it's a deriviative
ids := []string{}
for id := range o.Counts {
ids = append(ids, id)

View File

@ -11,11 +11,11 @@ import (
type ChannelList []*Channel
func (o *ChannelList) ToJson() string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func (o *ChannelList) Etag() string {
@ -55,11 +55,11 @@ func ChannelSliceFromJson(data io.Reader) []*Channel {
type ChannelListWithTeamData []*ChannelWithTeamData
func (o *ChannelListWithTeamData) ToJson() string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func (o *ChannelListWithTeamData) Etag() string {

View File

@ -24,36 +24,42 @@ const (
)
type ChannelUnread struct {
TeamId string `json:"team_id"`
ChannelId string `json:"channel_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
NotifyProps StringMap `json:"-"`
TeamId string `json:"team_id"`
ChannelId string `json:"channel_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
MsgCountRoot int64 `json:"msg_count_root"`
NotifyProps StringMap `json:"-"`
}
type ChannelUnreadAt struct {
TeamId string `json:"team_id"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
LastViewedAt int64 `json:"last_viewed_at"`
NotifyProps StringMap `json:"-"`
TeamId string `json:"team_id"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
MsgCountRoot int64 `json:"msg_count_root"`
LastViewedAt int64 `json:"last_viewed_at"`
NotifyProps StringMap `json:"-"`
}
type ChannelMember struct {
ChannelId string `json:"channel_id"`
UserId string `json:"user_id"`
Roles string `json:"roles"`
LastViewedAt int64 `json:"last_viewed_at"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
NotifyProps StringMap `json:"notify_props"`
LastUpdateAt int64 `json:"last_update_at"`
SchemeGuest bool `json:"scheme_guest"`
SchemeUser bool `json:"scheme_user"`
SchemeAdmin bool `json:"scheme_admin"`
ExplicitRoles string `json:"explicit_roles"`
ChannelId string `json:"channel_id"`
UserId string `json:"user_id"`
Roles string `json:"roles"`
LastViewedAt int64 `json:"last_viewed_at"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
MsgCountRoot int64 `json:"msg_count_root"`
NotifyProps StringMap `json:"notify_props"`
LastUpdateAt int64 `json:"last_update_at"`
SchemeGuest bool `json:"scheme_guest"`
SchemeUser bool `json:"scheme_user"`
SchemeAdmin bool `json:"scheme_admin"`
ExplicitRoles string `json:"explicit_roles"`
}
type ChannelMembers []ChannelMember
@ -65,11 +71,11 @@ type ChannelMemberForExport struct {
}
func (o *ChannelMembers) ToJson() string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func (o *ChannelUnread) ToJson() string {

View File

@ -11,18 +11,19 @@ import (
const CHANNEL_SEARCH_DEFAULT_LIMIT = 50
type ChannelSearch struct {
Term string `json:"term"`
ExcludeDefaultChannels bool `json:"exclude_default_channels"`
NotAssociatedToGroup string `json:"not_associated_to_group"`
TeamIds []string `json:"team_ids"`
GroupConstrained bool `json:"group_constrained"`
ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
Public bool `json:"public"`
Private bool `json:"private"`
IncludeDeleted bool `json:"include_deleted"`
Deleted bool `json:"deleted"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
Term string `json:"term"`
ExcludeDefaultChannels bool `json:"exclude_default_channels"`
NotAssociatedToGroup string `json:"not_associated_to_group"`
TeamIds []string `json:"team_ids"`
GroupConstrained bool `json:"group_constrained"`
ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
ExcludePolicyConstrained bool `json:"exclude_policy_constrained"`
Public bool `json:"public"`
Private bool `json:"private"`
IncludeDeleted bool `json:"include_deleted"`
Deleted bool `json:"deleted"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
}
// ToJson convert a Channel to a json string

View File

@ -47,6 +47,7 @@ type SidebarCategory struct {
Type SidebarCategoryType `json:"type"`
DisplayName string `json:"display_name"`
Muted bool `json:"muted"`
Collapsed bool `json:"collapsed"`
}
// SidebarCategoryWithChannels combines data from SidebarCategory table with the Channel IDs that belong to that category
@ -97,19 +98,19 @@ func (o SidebarCategoryWithChannels) ToJson() []byte {
}
func SidebarCategoriesWithChannelsToJson(o []*SidebarCategoryWithChannels) []byte {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return []byte("[]")
} else {
return b
}
return b
}
func (o OrderedSidebarCategories) ToJson() []byte {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return []byte("[]")
} else {
return b
}
return b
}
var categoryIdPattern = regexp.MustCompile("(favorites|channels|direct_messages)_[a-z0-9]{26}_[a-z0-9]{26}")

View File

@ -9,8 +9,9 @@ import (
)
type ChannelView struct {
ChannelId string `json:"channel_id"`
PrevChannelId string `json:"prev_channel_id"`
ChannelId string `json:"channel_id"`
PrevChannelId string `json:"prev_channel_id"`
CollapsedThreadsSupported bool `json:"collapsed_threads_supported"`
}
func (o *ChannelView) ToJson() string {

View File

@ -19,25 +19,29 @@ 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"
HEADER_REAL_IP = "X-Real-IP"
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
HEADER_TOKEN = "token"
HEADER_CSRF_TOKEN = "X-CSRF-Token"
HEADER_BEARER = "BEARER"
HEADER_AUTH = "Authorization"
HEADER_REQUESTED_WITH = "X-Requested-With"
HEADER_REQUESTED_WITH_XML = "XMLHttpRequest"
STATUS = "status"
STATUS_OK = "OK"
STATUS_FAIL = "FAIL"
STATUS_UNHEALTHY = "UNHEALTHY"
STATUS_REMOVE = "REMOVE"
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"
HEADER_REAL_IP = "X-Real-IP"
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
HEADER_TOKEN = "token"
HEADER_CSRF_TOKEN = "X-CSRF-Token"
HEADER_BEARER = "BEARER"
HEADER_AUTH = "Authorization"
HEADER_CLOUD_TOKEN = "X-Cloud-Token"
HEADER_REMOTECLUSTER_TOKEN = "X-RemoteCluster-Token"
HEADER_REMOTECLUSTER_ID = "X-RemoteCluster-Id"
HEADER_REQUESTED_WITH = "X-Requested-With"
HEADER_REQUESTED_WITH_XML = "XMLHttpRequest"
HEADER_RANGE = "Range"
STATUS = "status"
STATUS_OK = "OK"
STATUS_FAIL = "FAIL"
STATUS_UNHEALTHY = "UNHEALTHY"
STATUS_REMOVE = "REMOVE"
CLIENT_DIR = "client"
@ -93,9 +97,8 @@ func (c *Client4) boolString(value bool) string {
if value {
return "true"
} else {
return "false"
}
return "false"
}
func closeBody(r *http.Response) {
@ -189,12 +192,12 @@ func (c *Client4) GetUserRoute(userId string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId)
}
func (c *Client4) GetUserThreadsRoute(userId string) string {
return fmt.Sprintf(c.GetUsersRoute()+"/%v/threads", userId)
func (c *Client4) GetUserThreadsRoute(userID, teamID string) string {
return c.GetUserRoute(userID) + c.GetTeamRoute(teamID) + "/threads"
}
func (c *Client4) GetUserThreadRoute(userId, threadId string) string {
return fmt.Sprintf(c.GetUserThreadsRoute(userId)+"/%v", threadId)
func (c *Client4) GetUserThreadRoute(userId, teamId, threadId string) string {
return c.GetUserThreadsRoute(userId, teamId) + "/" + threadId
}
func (c *Client4) GetUserCategoryRoute(userID, teamID string) string {
@ -383,7 +386,11 @@ func (c *Client4) GetComplianceReportsRoute() string {
}
func (c *Client4) GetComplianceReportRoute(reportId string) string {
return fmt.Sprintf("/compliance/reports/%v", reportId)
return fmt.Sprintf("%s/%s", c.GetComplianceReportsRoute(), reportId)
}
func (c *Client4) GetComplianceReportDownloadRoute(reportId string) string {
return fmt.Sprintf("%s/%s/download", c.GetComplianceReportsRoute(), reportId)
}
func (c *Client4) GetOutgoingWebhooksRoute() string {
@ -422,6 +429,10 @@ func (c *Client4) GetDataRetentionRoute() string {
return "/data_retention"
}
func (c *Client4) GetDataRetentionPolicyRoute(policyID string) string {
return fmt.Sprintf(c.GetDataRetentionRoute()+"/policies/%v", policyID)
}
func (c *Client4) GetElasticsearchRoute() string {
return "/elasticsearch"
}
@ -542,6 +553,30 @@ func (c *Client4) GetGroupSyncablesRoute(groupID string, syncableType GroupSynca
return fmt.Sprintf("%s/%ss", c.GetGroupRoute(groupID), strings.ToLower(syncableType.String()))
}
func (c *Client4) GetImportsRoute() string {
return "/imports"
}
func (c *Client4) GetExportsRoute() string {
return "/exports"
}
func (c *Client4) GetExportRoute(name string) string {
return fmt.Sprintf(c.GetExportsRoute()+"/%v", name)
}
func (c *Client4) GetRemoteClusterRoute() string {
return "/remotecluster"
}
func (c *Client4) GetSharedChannelsRoute() string {
return "/sharedchannels"
}
func (c *Client4) GetPermissionsRoute() string {
return "/permissions"
}
func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag)
}
@ -550,6 +585,14 @@ func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError)
return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "")
}
func (c *Client4) doApiDeleteBytes(url string, data []byte) (*http.Response, *AppError) {
return c.doApiRequestBytes(http.MethodDelete, c.ApiUrl+url, data, "")
}
func (c *Client4) doApiPatchBytes(url string, data []byte) (*http.Response, *AppError) {
return c.doApiRequestBytes(http.MethodPatch, c.ApiUrl+url, data, "")
}
func (c *Client4) doApiPostBytes(url string, data []byte) (*http.Response, *AppError) {
return c.doApiRequestBytes(http.MethodPost, c.ApiUrl+url, data, "")
}
@ -567,24 +610,28 @@ func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) {
}
func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) {
return c.doApiRequestReader(method, url, strings.NewReader(data), etag)
return c.doApiRequestReader(method, url, strings.NewReader(data), map[string]string{HEADER_ETAG_CLIENT: etag})
}
func (c *Client4) DoApiRequestWithHeaders(method, url, data string, headers map[string]string) (*http.Response, *AppError) {
return c.doApiRequestReader(method, url, strings.NewReader(data), headers)
}
func (c *Client4) doApiRequestBytes(method, url string, data []byte, etag string) (*http.Response, *AppError) {
return c.doApiRequestReader(method, url, bytes.NewReader(data), etag)
return c.doApiRequestReader(method, url, bytes.NewReader(data), map[string]string{HEADER_ETAG_CLIENT: etag})
}
func (c *Client4) doApiRequestReader(method, url string, data io.Reader, etag string) (*http.Response, *AppError) {
func (c *Client4) doApiRequestReader(method, url string, data io.Reader, headers map[string]string) (*http.Response, *AppError) {
rq, err := http.NewRequest(method, url, data)
if err != nil {
return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), http.StatusBadRequest)
}
if len(etag) > 0 {
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
for k, v := range headers {
rq.Header.Set(k, v)
}
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -625,7 +672,7 @@ func (c *Client4) doUploadFile(url string, body io.Reader, contentType string, c
}
rq.Header.Set("Content-Type", contentType)
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -649,7 +696,7 @@ func (c *Client4) DoEmojiUploadFile(url string, data []byte, contentType string)
}
rq.Header.Set("Content-Type", contentType)
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -673,7 +720,7 @@ func (c *Client4) DoUploadImportTeam(url string, data []byte, contentType string
}
rq.Header.Set("Content-Type", contentType)
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -1393,14 +1440,20 @@ func (c *Client4) AttachDeviceId(deviceId string) (bool, *Response) {
// GetTeamsUnreadForUser will return an array with TeamUnread objects that contain the amount
// of unread messages and mentions the current user has for the teams it belongs to.
// An optional team ID can be set to exclude that team from the results. Must be authenticated.
func (c *Client4) GetTeamsUnreadForUser(userId, teamIdToExclude string) ([]*TeamUnread, *Response) {
var optional string
// An optional team ID can be set to exclude that team from the results.
// An optional boolean can be set to include collapsed thread unreads. Must be authenticated.
func (c *Client4) GetTeamsUnreadForUser(userId, teamIdToExclude string, includeCollapsedThreads bool) ([]*TeamUnread, *Response) {
query := url.Values{}
if teamIdToExclude != "" {
optional += fmt.Sprintf("?exclude_team=%s", url.QueryEscape(teamIdToExclude))
query.Set("exclude_team", teamIdToExclude)
}
r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/unread"+optional, "")
if includeCollapsedThreads {
query.Set("include_collapsed_threads", "true")
}
r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/unread?"+query.Encode(), "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
@ -1486,7 +1539,7 @@ func (c *Client4) SetProfileImage(userId string, data []byte) (bool, *Response)
}
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -1735,7 +1788,7 @@ func (c *Client4) SetBotIconImage(botUserId string, data []byte) (bool, *Respons
}
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -1822,6 +1875,18 @@ func (c *Client4) GetAllTeamsWithTotalCount(etag string, page int, perPage int)
return teamsListWithCount.Teams, teamsListWithCount.TotalCount, BuildResponse(r)
}
// GetAllTeamsExcludePolicyConstrained returns all teams which are not part of a data retention policy.
// Must be a system administrator.
func (c *Client4) GetAllTeamsExcludePolicyConstrained(etag string, page int, perPage int) ([]*Team, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&exclude_policy_constrained=%v", page, perPage, true)
r, err := c.DoApiGet(c.GetTeamsRoute()+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return TeamListFromJson(r.Body), BuildResponse(r)
}
// GetTeamByName returns a team based on the provided team name string.
func (c *Client4) GetTeamByName(name, etag string) (*Team, *Response) {
r, err := c.DoApiGet(c.GetTeamByNameRoute(name), etag)
@ -2269,7 +2334,7 @@ func (c *Client4) SetTeamIcon(teamId string, data []byte) (bool, *Response) {
}
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -2316,16 +2381,23 @@ func (c *Client4) RemoveTeamIcon(teamId string) (bool, *Response) {
// GetAllChannels get all the channels. Must be a system administrator.
func (c *Client4) GetAllChannels(page int, perPage int, etag string) (*ChannelListWithTeamData, *Response) {
return c.getAllChannels(page, perPage, etag, false)
return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{})
}
// GetAllChannelsIncludeDeleted get all the channels. Must be a system administrator.
func (c *Client4) GetAllChannelsIncludeDeleted(page int, perPage int, etag string) (*ChannelListWithTeamData, *Response) {
return c.getAllChannels(page, perPage, etag, true)
return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{IncludeDeleted: true})
}
func (c *Client4) getAllChannels(page int, perPage int, etag string, includeDeleted bool) (*ChannelListWithTeamData, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=%v", page, perPage, includeDeleted)
// GetAllChannelsExcludePolicyConstrained gets all channels which are not part of a data retention policy.
// Must be a system administrator.
func (c *Client4) GetAllChannelsExcludePolicyConstrained(page, perPage int, etag string) (*ChannelListWithTeamData, *Response) {
return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{ExcludePolicyConstrained: true})
}
func (c *Client4) getAllChannels(page int, perPage int, etag string, opts ChannelSearchOpts) (*ChannelListWithTeamData, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=%v&exclude_policy_constrained=%v",
page, perPage, opts.IncludeDeleted, opts.ExcludePolicyConstrained)
r, err := c.DoApiGet(c.GetChannelsRoute()+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -2849,8 +2921,9 @@ func (c *Client4) PatchPost(postId string, patch *PostPatch) (*Post, *Response)
}
// SetPostUnread marks channel where post belongs as unread on the time of the provided post.
func (c *Client4) SetPostUnread(userId string, postId string) *Response {
r, err := c.DoApiPost(c.GetUserRoute(userId)+c.GetPostRoute(postId)+"/set_unread", "")
func (c *Client4) SetPostUnread(userId string, postId string, collapsedThreadsSupported bool) *Response {
b, _ := json.Marshal(map[string]bool{"collapsed_threads_supported": collapsedThreadsSupported})
r, err := c.DoApiPost(c.GetUserRoute(userId)+c.GetPostRoute(postId)+"/set_unread", string(b))
if err != nil {
return BuildErrorResponse(r, err)
}
@ -2899,8 +2972,12 @@ func (c *Client4) DeletePost(postId string) (bool, *Response) {
}
// GetPostThread gets a post with all the other posts in the same thread.
func (c *Client4) GetPostThread(postId string, etag string) (*PostList, *Response) {
r, err := c.DoApiGet(c.GetPostRoute(postId)+"/thread", etag)
func (c *Client4) GetPostThread(postId string, etag string, collapsedThreads bool) (*PostList, *Response) {
url := c.GetPostRoute(postId) + "/thread"
if collapsedThreads {
url += "?collapsedThreads=true"
}
r, err := c.DoApiGet(url, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
@ -2909,8 +2986,11 @@ func (c *Client4) GetPostThread(postId string, etag string) (*PostList, *Respons
}
// GetPostsForChannel gets a page of posts with an array for ordering for a channel.
func (c *Client4) GetPostsForChannel(channelId string, page, perPage int, etag string) (*PostList, *Response) {
func (c *Client4) GetPostsForChannel(channelId string, page, perPage int, etag string, collapsedThreads bool) (*PostList, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
if collapsedThreads {
query += "&collapsedThreads=true"
}
r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -2961,8 +3041,11 @@ func (c *Client4) GetFlaggedPostsForUserInChannel(userId string, channelId strin
}
// GetPostsSince gets posts created after a specified time as Unix time in milliseconds.
func (c *Client4) GetPostsSince(channelId string, time int64) (*PostList, *Response) {
func (c *Client4) GetPostsSince(channelId string, time int64, collapsedThreads bool) (*PostList, *Response) {
query := fmt.Sprintf("?since=%v", time)
if collapsedThreads {
query += "&collapsedThreads=true"
}
r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, "")
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -2972,8 +3055,11 @@ func (c *Client4) GetPostsSince(channelId string, time int64) (*PostList, *Respo
}
// GetPostsAfter gets a page of posts that were posted after the post provided.
func (c *Client4) GetPostsAfter(channelId, postId string, page, perPage int, etag string) (*PostList, *Response) {
func (c *Client4) GetPostsAfter(channelId, postId string, page, perPage int, etag string, collapsedThreads bool) (*PostList, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&after=%v", page, perPage, postId)
if collapsedThreads {
query += "&collapsedThreads=true"
}
r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -2983,8 +3069,11 @@ func (c *Client4) GetPostsAfter(channelId, postId string, page, perPage int, eta
}
// GetPostsBefore gets a page of posts that were posted before the post provided.
func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, etag string) (*PostList, *Response) {
func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, etag string, collapsedThreads bool) (*PostList, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v&before=%v", page, perPage, postId)
if collapsedThreads {
query += "&collapsedThreads=true"
}
r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag)
if err != nil {
return nil, BuildErrorResponse(r, err)
@ -2994,14 +3083,36 @@ func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, et
}
// GetPostsAroundLastUnread gets a list of posts around last unread post by a user in a channel.
func (c *Client4) GetPostsAroundLastUnread(userId, channelId string, limitBefore, limitAfter int) (*PostList, *Response) {
func (c *Client4) GetPostsAroundLastUnread(userId, channelId string, limitBefore, limitAfter int, collapsedThreads bool) (*PostList, *Response) {
query := fmt.Sprintf("?limit_before=%v&limit_after=%v", limitBefore, limitAfter)
if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetChannelRoute(channelId)+"/posts/unread"+query, ""); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return PostListFromJson(r.Body), BuildResponse(r)
if collapsedThreads {
query += "&collapsedThreads=true"
}
r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetChannelRoute(channelId)+"/posts/unread"+query, "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return PostListFromJson(r.Body), BuildResponse(r)
}
// SearchFiles returns any posts with matching terms string.
func (c *Client4) SearchFiles(teamId string, terms string, isOrSearch bool) (*FileInfoList, *Response) {
params := SearchParameter{
Terms: &terms,
IsOrSearch: &isOrSearch,
}
return c.SearchFilesWithParams(teamId, &params)
}
// SearchFilesWithParams returns any posts with matching terms string.
func (c *Client4) SearchFilesWithParams(teamId string, params *SearchParameter) (*FileInfoList, *Response) {
r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/files/search", params.SearchParameterToJson())
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return FileInfoListFromJson(r.Body), BuildResponse(r)
}
// SearchPosts returns any posts with matching terms string.
@ -3251,6 +3362,21 @@ func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo,
// General/System Section
// GenerateSupportPacket downloads the generated support packet
func (c *Client4) GenerateSupportPacket() ([]byte, *Response) {
r, appErr := c.DoApiGet(c.GetSystemRoute()+"/support_packet", "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, BuildErrorResponse(r, NewAppError("GetFile", "model.client.read_job_result_file.app_error", nil, err.Error(), r.StatusCode))
}
return data, BuildResponse(r)
}
// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above.
func (c *Client4) GetPing() (string, *Response) {
r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", "")
@ -3448,7 +3574,7 @@ func (c *Client4) UploadLicenseFile(data []byte) (bool, *Response) {
}
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -3815,6 +3941,28 @@ func (c *Client4) GetSamlMetadataFromIdp(samlMetadataURL string) (*SamlMetadataR
return SamlMetadataResponseFromJson(r.Body), BuildResponse(r)
}
// ResetSamlAuthDataToEmail resets the AuthData field of SAML users to their Email.
func (c *Client4) ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (int64, *Response) {
params := map[string]interface{}{
"include_deleted": includeDeleted,
"dry_run": dryRun,
"user_ids": userIDs,
}
b, _ := json.Marshal(params)
r, err := c.doApiPostBytes(c.GetSamlRoute()+"/reset_auth_data", b)
if err != nil {
return 0, BuildErrorResponse(r, err)
}
defer closeBody(r)
respBody := map[string]int64{}
jsonErr := json.NewDecoder(r.Body).Decode(&respBody)
if jsonErr != nil {
appErr := NewAppError("Api4.ResetSamlAuthDataToEmail", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
return 0, BuildErrorResponse(r, appErr)
}
return respBody["num_affected"], BuildResponse(r)
}
// Compliance Section
// CreateComplianceReport creates an incoming webhook for a channel.
@ -3850,12 +3998,12 @@ func (c *Client4) GetComplianceReport(reportId string) (*Compliance, *Response)
// DownloadComplianceReport returns a full compliance report as a file.
func (c *Client4) DownloadComplianceReport(reportId string) ([]byte, *Response) {
rq, err := http.NewRequest("GET", c.ApiUrl+c.GetComplianceReportRoute(reportId), nil)
rq, err := http.NewRequest("GET", c.ApiUrl+c.GetComplianceReportDownloadRoute(reportId), nil)
if err != nil {
return nil, &Response{Error: NewAppError("DownloadComplianceReport", "model.client.connecting.app_error", nil, err.Error(), http.StatusBadRequest)}
}
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
}
@ -3892,8 +4040,13 @@ func (c *Client4) GetClusterStatus() ([]*ClusterInfo, *Response) {
// LDAP Section
// SyncLdap will force a sync with the configured LDAP server.
func (c *Client4) SyncLdap() (bool, *Response) {
r, err := c.DoApiPost(c.GetLdapRoute()+"/sync", "")
// If includeRemovedMembers is true, then group members who left or were removed from a
// synced team/channel will be re-joined; otherwise, they will be excluded.
func (c *Client4) SyncLdap(includeRemovedMembers bool) (bool, *Response) {
reqBody, _ := json.Marshal(map[string]interface{}{
"include_removed_members": includeRemovedMembers,
})
r, err := c.doApiPostBytes(c.GetLdapRoute()+"/sync", reqBody)
if err != nil {
return false, BuildErrorResponse(r, err)
}
@ -4225,7 +4378,7 @@ func (c *Client4) UploadBrandImage(data []byte) (bool, *Response) {
}
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -4380,7 +4533,7 @@ func (c *Client4) GetOAuthAccessToken(data url.Values) (*AccessResponse, *Respon
}
rq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -4434,14 +4587,236 @@ func (c *Client4) PurgeBleveIndexes() (bool, *Response) {
// Data Retention Section
// GetDataRetentionPolicy will get the current server data retention policy details.
func (c *Client4) GetDataRetentionPolicy() (*DataRetentionPolicy, *Response) {
// GetDataRetentionPolicy will get the current global data retention policy details.
func (c *Client4) GetDataRetentionPolicy() (*GlobalRetentionPolicy, *Response) {
r, err := c.DoApiGet(c.GetDataRetentionRoute()+"/policy", "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return DataRetentionPolicyFromJson(r.Body), BuildResponse(r)
return GlobalRetentionPolicyFromJson(r.Body), BuildResponse(r)
}
// GetDataRetentionPolicyByID will get the details for the granular data retention policy with the specified ID.
func (c *Client4) GetDataRetentionPolicyByID(policyID string) (*RetentionPolicyWithTeamAndChannelCounts, *Response) {
r, appErr := c.DoApiGet(c.GetDataRetentionPolicyRoute(policyID), "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
policy, err := RetentionPolicyWithTeamAndChannelCountsFromJson(r.Body)
if err != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.GetDataRetentionPolicyByID", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode))
}
return policy, BuildResponse(r)
}
// GetDataRetentionPoliciesCount will get the total number of granular data retention policies.
func (c *Client4) GetDataRetentionPoliciesCount() (int64, *Response) {
type CountBody struct {
TotalCount int64 `json:"total_count"`
}
r, appErr := c.DoApiGet(c.GetDataRetentionRoute()+"/policies_count", "")
if appErr != nil {
return 0, BuildErrorResponse(r, appErr)
}
var countObj CountBody
jsonErr := json.NewDecoder(r.Body).Decode(&countObj)
if jsonErr != nil {
return 0, BuildErrorResponse(r, NewAppError("Client4.GetDataRetentionPoliciesCount", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return countObj.TotalCount, BuildResponse(r)
}
// GetDataRetentionPolicies will get the current granular data retention policies' details.
func (c *Client4) GetDataRetentionPolicies(page, perPage int) (*RetentionPolicyWithTeamAndChannelCountsList, *Response) {
query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage)
r, appErr := c.DoApiGet(c.GetDataRetentionRoute()+"/policies"+query, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
policies, err := RetentionPolicyWithTeamAndChannelCountsListFromJson(r.Body)
if err != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.GetDataRetentionPolicies", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode))
}
return policies, BuildResponse(r)
}
// CreateDataRetentionPolicy will create a new granular data retention policy which will be applied to
// the specified teams and channels. The Id field of `policy` must be empty.
func (c *Client4) CreateDataRetentionPolicy(policy *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response) {
r, appErr := c.doApiPostBytes(c.GetDataRetentionRoute()+"/policies", policy.ToJson())
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
newPolicy, err := RetentionPolicyWithTeamAndChannelCountsFromJson(r.Body)
if err != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.CreateDataRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode))
}
return newPolicy, BuildResponse(r)
}
// DeleteDataRetentionPolicy will delete the granular data retention policy with the specified ID.
func (c *Client4) DeleteDataRetentionPolicy(policyID string) *Response {
r, appErr := c.DoApiDelete(c.GetDataRetentionPolicyRoute(policyID))
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
// PatchDataRetentionPolicy will patch the granular data retention policy with the specified ID.
// The Id field of `patch` must be non-empty.
func (c *Client4) PatchDataRetentionPolicy(patch *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response) {
r, appErr := c.doApiPatchBytes(c.GetDataRetentionPolicyRoute(patch.ID), patch.ToJson())
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
policy, err := RetentionPolicyWithTeamAndChannelCountsFromJson(r.Body)
if err != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.PatchDataRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode))
}
return policy, BuildResponse(r)
}
// GetTeamsForRetentionPolicy will get the teams to which the specified policy is currently applied.
func (c *Client4) GetTeamsForRetentionPolicy(policyID string, page, perPage int) (*TeamsWithCount, *Response) {
query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage)
r, appErr := c.DoApiGet(c.GetDataRetentionPolicyRoute(policyID)+"/teams"+query, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
var teams *TeamsWithCount
jsonErr := json.NewDecoder(r.Body).Decode(&teams)
if jsonErr != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.GetTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return teams, BuildResponse(r)
}
// SearchTeamsForRetentionPolicy will search the teams to which the specified policy is currently applied.
func (c *Client4) SearchTeamsForRetentionPolicy(policyID string, term string) ([]*Team, *Response) {
body, _ := json.Marshal(map[string]interface{}{"term": term})
r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/teams/search", body)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
var teams []*Team
jsonErr := json.NewDecoder(r.Body).Decode(&teams)
if jsonErr != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.SearchTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return teams, BuildResponse(r)
}
// AddTeamsToRetentionPolicy will add the specified teams to the granular data retention policy
// with the specified ID.
func (c *Client4) AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *Response {
body, _ := json.Marshal(teamIDs)
r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/teams", body)
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
// RemoveTeamsFromRetentionPolicy will remove the specified teams from the granular data retention policy
// with the specified ID.
func (c *Client4) RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) *Response {
body, _ := json.Marshal(teamIDs)
r, appErr := c.doApiDeleteBytes(c.GetDataRetentionPolicyRoute(policyID)+"/teams", body)
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
// GetChannelsForRetentionPolicy will get the channels to which the specified policy is currently applied.
func (c *Client4) GetChannelsForRetentionPolicy(policyID string, page, perPage int) (*ChannelsWithCount, *Response) {
query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage)
r, appErr := c.DoApiGet(c.GetDataRetentionPolicyRoute(policyID)+"/channels"+query, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
var channels *ChannelsWithCount
jsonErr := json.NewDecoder(r.Body).Decode(&channels)
if jsonErr != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.GetChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return channels, BuildResponse(r)
}
// SearchChannelsForRetentionPolicy will search the channels to which the specified policy is currently applied.
func (c *Client4) SearchChannelsForRetentionPolicy(policyID string, term string) (ChannelListWithTeamData, *Response) {
body, _ := json.Marshal(map[string]interface{}{"term": term})
r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/channels/search", body)
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
var channels ChannelListWithTeamData
jsonErr := json.NewDecoder(r.Body).Decode(&channels)
if jsonErr != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.SearchChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return channels, BuildResponse(r)
}
// AddChannelsToRetentionPolicy will add the specified channels to the granular data retention policy
// with the specified ID.
func (c *Client4) AddChannelsToRetentionPolicy(policyID string, channelIDs []string) *Response {
body, _ := json.Marshal(channelIDs)
r, appErr := c.doApiPostBytes(c.GetDataRetentionPolicyRoute(policyID)+"/channels", body)
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
// RemoveChannelsFromRetentionPolicy will remove the specified channels from the granular data retention policy
// with the specified ID.
func (c *Client4) RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *Response {
body, _ := json.Marshal(channelIDs)
r, appErr := c.doApiDeleteBytes(c.GetDataRetentionPolicyRoute(policyID)+"/channels", body)
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
// GetTeamPoliciesForUser will get the data retention policies for the teams to which a user belongs.
func (c *Client4) GetTeamPoliciesForUser(userID string, offset, limit int) (*RetentionPolicyForTeamList, *Response) {
r, appErr := c.DoApiGet(c.GetUserRoute(userID)+"/data_retention/team_policies", "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
var teams RetentionPolicyForTeamList
jsonErr := json.NewDecoder(r.Body).Decode(&teams)
if jsonErr != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.GetTeamPoliciesForUser", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return &teams, BuildResponse(r)
}
// GetChannelPoliciesForUser will get the data retention policies for the channels to which a user belongs.
func (c *Client4) GetChannelPoliciesForUser(userID string, offset, limit int) (*RetentionPolicyForChannelList, *Response) {
r, appErr := c.DoApiGet(c.GetUserRoute(userID)+"/data_retention/channel_policies", "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
var channels RetentionPolicyForChannelList
jsonErr := json.NewDecoder(r.Body).Decode(&channels)
if jsonErr != nil {
return nil, BuildErrorResponse(r, NewAppError("Client4.GetChannelPoliciesForUser", "model.utils.decode_json.app_error", nil, jsonErr.Error(), r.StatusCode))
}
return &channels, BuildResponse(r)
}
// Commands Section
@ -5019,7 +5394,7 @@ func (c *Client4) uploadPlugin(file io.Reader, force bool) (*Manifest, *Response
}
rq.Header.Set("Content-Type", writer.FormDataContentType())
if len(c.AuthToken) > 0 {
if c.AuthToken != "" {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
}
@ -5436,7 +5811,7 @@ func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezon
// RequestTrialLicense will request a trial license and install it in the server
func (c *Client4) RequestTrialLicense(users int) (bool, *Response) {
b, _ := json.Marshal(map[string]int{"users": users})
b, _ := json.Marshal(map[string]interface{}{"users": users, "terms_accepted": true})
r, err := c.DoApiPost("/trial-license", string(b))
if err != nil {
return false, BuildErrorResponse(r, err)
@ -5626,7 +6001,7 @@ func (c *Client4) GetUploadsForUser(userId string) ([]*UploadSession, *Response)
// a FileInfo object.
func (c *Client4) UploadData(uploadId string, data io.Reader) (*FileInfo, *Response) {
url := c.GetUploadRoute(uploadId)
r, err := c.doApiRequestReader("POST", c.ApiUrl+url, data, "")
r, err := c.doApiRequestReader("POST", c.ApiUrl+url, data, nil)
if err != nil {
return nil, BuildErrorResponse(r, err)
}
@ -5710,6 +6085,18 @@ func (c *Client4) GetSubscription() (*Subscription, *Response) {
return subscription, BuildResponse(r)
}
func (c *Client4) GetSubscriptionStats() (*SubscriptionStats, *Response) {
r, appErr := c.DoApiGet(c.GetCloudRoute()+"/subscription/stats", "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
var stats *SubscriptionStats
json.NewDecoder(r.Body).Decode(&stats)
return stats, BuildResponse(r)
}
func (c *Client4) GetInvoicesForSubscription() ([]*Invoice, *Response) {
r, appErr := c.DoApiGet(c.GetCloudRoute()+"/subscription/invoices", "")
if appErr != nil {
@ -5753,13 +6140,62 @@ func (c *Client4) UpdateCloudCustomerAddress(address *Address) (*CloudCustomer,
return customer, BuildResponse(r)
}
func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Threads, *Response) {
func (c *Client4) ListImports() ([]string, *Response) {
r, err := c.DoApiGet(c.GetImportsRoute(), "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return ArrayFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) ListExports() ([]string, *Response) {
r, err := c.DoApiGet(c.GetExportsRoute(), "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return ArrayFromJson(r.Body), BuildResponse(r)
}
func (c *Client4) DeleteExport(name string) (bool, *Response) {
r, err := c.DoApiDelete(c.GetExportRoute(name))
if err != nil {
return false, BuildErrorResponse(r, err)
}
defer closeBody(r)
return CheckStatusOK(r), BuildResponse(r)
}
func (c *Client4) DownloadExport(name string, wr io.Writer, offset int64) (int64, *Response) {
var headers map[string]string
if offset > 0 {
headers = map[string]string{
HEADER_RANGE: fmt.Sprintf("bytes=%d-", offset),
}
}
r, appErr := c.DoApiRequestWithHeaders(http.MethodGet, c.ApiUrl+c.GetExportRoute(name), "", headers)
if appErr != nil {
return 0, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
n, err := io.Copy(wr, r.Body)
if err != nil {
return n, BuildErrorResponse(r, NewAppError("DownloadExport", "model.client.copy.app_error", nil, err.Error(), r.StatusCode))
}
return n, BuildResponse(r)
}
func (c *Client4) GetUserThreads(userId, teamId string, options GetUserThreadsOpts) (*Threads, *Response) {
v := url.Values{}
if options.Since != 0 {
v.Set("since", fmt.Sprintf("%d", options.Since))
}
if options.Page != 0 {
v.Set("page", fmt.Sprintf("%d", options.Page))
if options.Before != "" {
v.Set("before", options.Before)
}
if options.After != "" {
v.Set("after", options.After)
}
if options.PageSize != 0 {
v.Set("pageSize", fmt.Sprintf("%d", options.PageSize))
@ -5770,8 +6206,10 @@ func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Th
if options.Deleted {
v.Set("deleted", "true")
}
url := c.GetUserThreadsRoute(userId)
if options.Unread {
v.Set("unread", "true")
}
url := c.GetUserThreadsRoute(userId, teamId)
if len(v) > 0 {
url += "?" + v.Encode()
}
@ -5788,8 +6226,25 @@ func (c *Client4) GetUserThreads(userId string, options GetUserThreadsOpts) (*Th
return &threads, BuildResponse(r)
}
func (c *Client4) UpdateThreadsReadForUser(userId string, timestamp int64) *Response {
r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadsRoute(userId), timestamp), "")
func (c *Client4) GetUserThread(userId, teamId, threadId string, extended bool) (*ThreadResponse, *Response) {
url := c.GetUserThreadRoute(userId, teamId, threadId)
if extended {
url += "?extended=true"
}
r, appErr := c.DoApiGet(url, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
var thread ThreadResponse
json.NewDecoder(r.Body).Decode(&thread)
return &thread, BuildResponse(r)
}
func (c *Client4) UpdateThreadsReadForUser(userId, teamId string) *Response {
r, appErr := c.DoApiPut(fmt.Sprintf("%s/read", c.GetUserThreadsRoute(userId, teamId)), "")
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
@ -5798,23 +6253,25 @@ func (c *Client4) UpdateThreadsReadForUser(userId string, timestamp int64) *Resp
return BuildResponse(r)
}
func (c *Client4) UpdateThreadReadForUser(userId, threadId string, timestamp int64) *Response {
r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadRoute(userId, threadId), timestamp), "")
func (c *Client4) UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) (*ThreadResponse, *Response) {
r, appErr := c.DoApiPut(fmt.Sprintf("%s/read/%d", c.GetUserThreadRoute(userId, teamId, threadId), timestamp), "")
if appErr != nil {
return BuildErrorResponse(r, appErr)
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
var thread ThreadResponse
json.NewDecoder(r.Body).Decode(&thread)
return BuildResponse(r)
return &thread, BuildResponse(r)
}
func (c *Client4) UpdateThreadFollowForUser(userId, threadId string, state bool) *Response {
func (c *Client4) UpdateThreadFollowForUser(userId, teamId, threadId string, state bool) *Response {
var appErr *AppError
var r *http.Response
if state {
r, appErr = c.DoApiPut(c.GetUserThreadRoute(userId, threadId)+"/following", "")
r, appErr = c.DoApiPut(c.GetUserThreadRoute(userId, teamId, threadId)+"/following", "")
} else {
r, appErr = c.DoApiDelete(c.GetUserThreadRoute(userId, threadId) + "/following")
r, appErr = c.DoApiDelete(c.GetUserThreadRoute(userId, teamId, threadId) + "/following")
}
if appErr != nil {
return BuildErrorResponse(r, appErr)
@ -5823,3 +6280,64 @@ func (c *Client4) UpdateThreadFollowForUser(userId, threadId string, state bool)
return BuildResponse(r)
}
func (c *Client4) SendAdminUpgradeRequestEmail() *Response {
r, appErr := c.DoApiPost(c.GetCloudRoute()+"/subscription/limitreached/invite", "")
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
func (c *Client4) SendAdminUpgradeRequestEmailOnJoin() *Response {
r, appErr := c.DoApiPost(c.GetCloudRoute()+"/subscription/limitreached/join", "")
if appErr != nil {
return BuildErrorResponse(r, appErr)
}
defer closeBody(r)
return BuildResponse(r)
}
func (c *Client4) GetAllSharedChannels(teamID string, page, perPage int) ([]*SharedChannel, *Response) {
url := fmt.Sprintf("%s/%s?page=%d&per_page=%d", c.GetSharedChannelsRoute(), teamID, page, perPage)
r, appErr := c.DoApiGet(url, "")
if appErr != nil {
return nil, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
var channels []*SharedChannel
json.NewDecoder(r.Body).Decode(&channels)
return channels, BuildResponse(r)
}
func (c *Client4) GetRemoteClusterInfo(remoteID string) (RemoteClusterInfo, *Response) {
url := fmt.Sprintf("%s/remote_info/%s", c.GetSharedChannelsRoute(), remoteID)
r, appErr := c.DoApiGet(url, "")
if appErr != nil {
return RemoteClusterInfo{}, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
var rci RemoteClusterInfo
json.NewDecoder(r.Body).Decode(&rci)
return rci, BuildResponse(r)
}
func (c *Client4) GetAncillaryPermissions(subsectionPermissions []string) ([]string, *Response) {
var returnedPermissions []string
url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.GetPermissionsRoute(), strings.Join(subsectionPermissions, ","))
r, appErr := c.DoApiGet(url, "")
if appErr != nil {
return returnedPermissions, BuildErrorResponse(r, appErr)
}
defer closeBody(r)
json.NewDecoder(r.Body).Decode(&returnedPermissions)
return returnedPermissions, BuildResponse(r)
}

View File

@ -3,13 +3,53 @@
package model
import "strings"
const (
EventTypeFailedPayment = "failed-payment"
EventTypeFailedPaymentNoCard = "failed-payment-no-card"
EventTypeSendAdminWelcomeEmail = "send-admin-welcome-email"
EventTypeTrialWillEnd = "trial-will-end"
EventTypeTrialEnded = "trial-ended"
JoinLimitation = "join"
InviteLimitation = "invite"
)
var MockCWS string
type BillingScheme string
const (
BillingSchemePerSeat = BillingScheme("per_seat")
BillingSchemeFlatFee = BillingScheme("flat_fee")
)
type RecurringInterval string
const (
RecurringIntervalYearly = RecurringInterval("year")
RecurringIntervalMonthly = RecurringInterval("month")
)
type SubscriptionFamily string
const (
SubscriptionFamilyCloud = SubscriptionFamily("cloud")
SubscriptionFamilyOnPrem = SubscriptionFamily("on-prem")
)
// Product model represents a product on the cloud system.
type Product struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
PricePerSeat float64 `json:"price_per_seat"`
AddOns []*AddOn `json:"add_ons"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
PricePerSeat float64 `json:"price_per_seat"`
AddOns []*AddOn `json:"add_ons"`
SKU string `json:"sku"`
PriceID string `json:"price_id"`
Family SubscriptionFamily `json:"product_family"`
RecurringInterval RecurringInterval `json:"recurring_interval"`
BillingScheme BillingScheme `json:"billing_scheme"`
}
// AddOn represents an addon to a product.
@ -85,6 +125,13 @@ type Subscription struct {
DNS string `json:"dns"`
IsPaidTier string `json:"is_paid_tier"`
LastInvoice *Invoice `json:"last_invoice"`
IsFreeTrial string `json:"is_free_trial"`
TrialEndAt int64 `json:"trial_end_at"`
}
// GetWorkSpaceNameFromDNS returns the work space name. For example from test.mattermost.cloud.com, it returns test
func (s *Subscription) GetWorkSpaceNameFromDNS() string {
return strings.Split(s.DNS, ".")[0]
}
// Invoice model represents a cloud invoice
@ -112,3 +159,30 @@ type InvoiceLineItem struct {
Type string `json:"type"`
Metadata map[string]interface{} `json:"metadata"`
}
type CWSWebhookPayload struct {
Event string `json:"event"`
FailedPayment *FailedPayment `json:"failed_payment"`
CloudWorkspaceOwner *CloudWorkspaceOwner `json:"cloud_workspace_owner"`
SubscriptionTrialEndUnixTimeStamp int64 `json:"trial_end_time_stamp"`
}
type FailedPayment struct {
CardBrand string `json:"card_brand"`
LastFour int `json:"last_four"`
FailureMessage string `json:"failure_message"`
}
// CloudWorkspaceOwner is part of the CWS Webhook payload that contains information about the user that created the workspace from the CWS
type CloudWorkspaceOwner struct {
UserName string `json:"username"`
}
type SubscriptionStats struct {
RemainingSeats int `json:"remaining_seats"`
IsPaidTier string `json:"is_paid_tier"`
IsFreeTrial string `json:"is_free_trial"`
}
type SubscriptionChange struct {
ProductID string `json:"product_id"`
}

View File

@ -39,7 +39,7 @@ func (o *ClusterDiscovery) PreSave() {
func (o *ClusterDiscovery) AutoFillHostname() {
// attempt to set the hostname from the OS
if len(o.Hostname) == 0 {
if o.Hostname == "" {
if hn, err := os.Hostname(); err == nil {
o.Hostname = hn
}
@ -48,8 +48,8 @@ func (o *ClusterDiscovery) AutoFillHostname() {
func (o *ClusterDiscovery) AutoFillIpAddress(iface string, ipAddress string) {
// attempt to set the hostname to the first non-local IP address
if len(o.Hostname) == 0 {
if len(ipAddress) > 0 {
if o.Hostname == "" {
if ipAddress != "" {
o.Hostname = ipAddress
} else {
o.Hostname = GetServerIpAddress(iface)
@ -93,15 +93,15 @@ func (o *ClusterDiscovery) IsValid() *AppError {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ClusterName) == 0 {
if o.ClusterName == "" {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Type) == 0 {
if o.Type == "" {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.type.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Hostname) == 0 {
if o.Hostname == "" {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.hostname.app_error", nil, "", http.StatusBadRequest)
}

View File

@ -16,15 +16,15 @@ type ClusterInfo struct {
Hostname string `json:"hostname"`
}
func (me *ClusterInfo) ToJson() string {
b, _ := json.Marshal(me)
func (ci *ClusterInfo) ToJson() string {
b, _ := json.Marshal(ci)
return string(b)
}
func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
var me *ClusterInfo
json.NewDecoder(data).Decode(&me)
return me
var ci *ClusterInfo
json.NewDecoder(data).Decode(&ci)
return ci
}
func ClusterInfosToJson(objmap []*ClusterInfo) string {
@ -38,7 +38,6 @@ func ClusterInfosFromJson(data io.Reader) []*ClusterInfo {
var objmap []*ClusterInfo
if err := decoder.Decode(&objmap); err != nil {
return make([]*ClusterInfo, 0)
} else {
return objmap
}
return objmap
}

View File

@ -40,6 +40,7 @@ const (
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_ALL_USERS = "inv_all_user_sessions"
CLUSTER_EVENT_INSTALL_PLUGIN = "install_plugin"
CLUSTER_EVENT_REMOVE_PLUGIN = "remove_plugin"
CLUSTER_EVENT_PLUGIN_EVENT = "plugin_event"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_TERMS_OF_SERVICE = "inv_terms_of_service"
CLUSTER_EVENT_BUSY_STATE_CHANGED = "busy_state_change"

View File

@ -15,13 +15,13 @@ type ClusterStats struct {
TotalMasterDbConnections int `json:"total_master_db_connections"`
}
func (me *ClusterStats) ToJson() string {
b, _ := json.Marshal(me)
func (cs *ClusterStats) ToJson() string {
b, _ := json.Marshal(cs)
return string(b)
}
func ClusterStatsFromJson(data io.Reader) *ClusterStats {
var me *ClusterStats
json.NewDecoder(data).Decode(&me)
return me
var cs *ClusterStats
json.NewDecoder(data).Decode(&cs)
return cs
}

View File

@ -105,7 +105,7 @@ func (o *Command) IsValid() *AppError {
return NewAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "", http.StatusBadRequest)
}
if len(o.URL) == 0 || len(o.URL) > 1024 {
if o.URL == "" || len(o.URL) > 1024 {
return NewAppError("Command.IsValid", "model.command.is_valid.url.app_error", nil, "", http.StatusBadRequest)
}

View File

@ -7,21 +7,21 @@ import (
"encoding/json"
"io"
goi18n "github.com/mattermost/go-i18n/i18n"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
)
type CommandArgs struct {
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
TriggerId string `json:"trigger_id,omitempty"`
Command string `json:"command"`
SiteURL string `json:"-"`
T goi18n.TranslateFunc `json:"-"`
UserMentions UserMentionMap `json:"-"`
ChannelMentions ChannelMentionMap `json:"-"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
TriggerId string `json:"trigger_id,omitempty"`
Command string `json:"command"`
SiteURL string `json:"-"`
T i18n.TranslateFunc `json:"-"`
UserMentions UserMentionMap `json:"-"`
ChannelMentions ChannelMentionMap `json:"-"`
// DO NOT USE Session field is deprecated. MM-26398
Session Session `json:"-"`

View File

@ -52,7 +52,7 @@ type AutocompleteArg struct {
HelpText string
// Type of the argument
Type AutocompleteArgType
// Required determins if argument is optional or not.
// Required determines if argument is optional or not.
Required bool
// Actual data of the argument (depends on the Type)
Data interface{}

View File

@ -62,7 +62,7 @@ func CommandResponseFromJson(data io.Reader) (*CommandResponse, error) {
var o CommandResponse
err = json.Unmarshal(b, &o)
if err != nil {
return nil, jsonutils.HumanizeJsonError(err, b)
return nil, jsonutils.HumanizeJSONError(err, b)
}
o.Attachments = StringifySlackFieldValue(o.Attachments)

View File

@ -53,11 +53,11 @@ func (o *CommandWebhook) IsValid() *AppError {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.RootId) != 0 && !IsValidId(o.RootId) {
if o.RootId != "" && !IsValidId(o.RootId) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.root_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ParentId) != 0 && !IsValidId(o.ParentId) {
if o.ParentId != "" && !IsValidId(o.ParentId) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.parent_id.app_error", nil, "", http.StatusBadRequest)
}

View File

@ -37,6 +37,19 @@ type Compliance struct {
type Compliances []Compliance
// ComplianceExportCursor is used for paginated iteration of posts
// for compliance export.
// We need to keep track of the last post ID in addition to the last post
// CreateAt to break ties when two posts have the same CreateAt.
type ComplianceExportCursor struct {
LastChannelsQueryPostCreateAt int64
LastChannelsQueryPostID string
ChannelsQueryCompleted bool
LastDirectMessagesQueryPostCreateAt int64
LastDirectMessagesQueryPostID string
DirectMessagesQueryCompleted bool
}
func (c *Compliance) ToJson() string {
b, _ := json.Marshal(c)
return string(b)
@ -58,6 +71,11 @@ func (c *Compliance) PreSave() {
c.CreateAt = GetMillis()
}
func (c *Compliance) DeepCopy() *Compliance {
copy := *c
return &copy
}
func (c *Compliance) JobName() string {
jobName := c.Type
if c.Type == COMPLIANCE_TYPE_DAILY {
@ -79,7 +97,7 @@ func (c *Compliance) IsValid() *AppError {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if len(c.Desc) > 512 || len(c.Desc) == 0 {
if len(c.Desc) > 512 || c.Desc == "" {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "", http.StatusBadRequest)
}
@ -105,11 +123,11 @@ func ComplianceFromJson(data io.Reader) *Compliance {
}
func (c Compliances) ToJson() string {
if b, err := json.Marshal(c); err != nil {
b, err := json.Marshal(c)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func CompliancesFromJson(data io.Reader) Compliances {

View File

@ -53,6 +53,7 @@ func CompliancePostHeader() []string {
"UserUsername",
"UserEmail",
"UserNickname",
"UserType",
"PostId",
"PostCreateAt",
@ -66,61 +67,58 @@ func CompliancePostHeader() []string {
"PostProps",
"PostHashtags",
"PostFileIds",
"UserType",
}
}
func cleanComplianceStrings(in string) string {
if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched {
return "'" + in
} else {
return in
}
return in
}
func (me *CompliancePost) Row() []string {
func (cp *CompliancePost) Row() []string {
postDeleteAt := ""
if me.PostDeleteAt > 0 {
postDeleteAt = time.Unix(0, me.PostDeleteAt*int64(1000*1000)).Format(time.RFC3339)
if cp.PostDeleteAt > 0 {
postDeleteAt = time.Unix(0, cp.PostDeleteAt*int64(1000*1000)).Format(time.RFC3339)
}
postUpdateAt := ""
if me.PostUpdateAt != me.PostCreateAt {
postUpdateAt = time.Unix(0, me.PostUpdateAt*int64(1000*1000)).Format(time.RFC3339)
if cp.PostUpdateAt != cp.PostCreateAt {
postUpdateAt = time.Unix(0, cp.PostUpdateAt*int64(1000*1000)).Format(time.RFC3339)
}
userType := "user"
if me.IsBot {
if cp.IsBot {
userType = "bot"
}
return []string{
cleanComplianceStrings(me.TeamName),
cleanComplianceStrings(me.TeamDisplayName),
cleanComplianceStrings(cp.TeamName),
cleanComplianceStrings(cp.TeamDisplayName),
cleanComplianceStrings(me.ChannelName),
cleanComplianceStrings(me.ChannelDisplayName),
cleanComplianceStrings(me.ChannelType),
cleanComplianceStrings(cp.ChannelName),
cleanComplianceStrings(cp.ChannelDisplayName),
cleanComplianceStrings(cp.ChannelType),
cleanComplianceStrings(me.UserUsername),
cleanComplianceStrings(me.UserEmail),
cleanComplianceStrings(me.UserNickname),
cleanComplianceStrings(cp.UserUsername),
cleanComplianceStrings(cp.UserEmail),
cleanComplianceStrings(cp.UserNickname),
userType,
me.PostId,
time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339),
cp.PostId,
time.Unix(0, cp.PostCreateAt*int64(1000*1000)).Format(time.RFC3339),
postUpdateAt,
postDeleteAt,
me.PostRootId,
me.PostParentId,
me.PostOriginalId,
cleanComplianceStrings(me.PostMessage),
me.PostType,
me.PostProps,
me.PostHashtags,
me.PostFileIds,
cp.PostRootId,
cp.PostParentId,
cp.PostOriginalId,
cleanComplianceStrings(cp.PostMessage),
cp.PostType,
cp.PostProps,
cp.PostHashtags,
cp.PostFileIds,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,141 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"time"
)
const (
UserPropsKeyCustomStatus = "customStatus"
CustomStatusTextMaxRunes = 100
MaxRecentCustomStatuses = 5
DefaultCustomStatusEmoji = "speech_balloon"
)
var validCustomStatusDuration = map[string]bool{
"thirty_minutes": true,
"one_hour": true,
"four_hours": true,
"today": true,
"this_week": true,
"date_and_time": true,
}
type CustomStatus struct {
Emoji string `json:"emoji"`
Text string `json:"text"`
Duration string `json:"duration"`
ExpiresAt time.Time `json:"expires_at"`
}
func (cs *CustomStatus) PreSave() {
if cs.Emoji == "" {
cs.Emoji = DefaultCustomStatusEmoji
}
if cs.Duration == "" && !cs.ExpiresAt.Before(time.Now()) {
cs.Duration = "date_and_time"
}
runes := []rune(cs.Text)
if len(runes) > CustomStatusTextMaxRunes {
cs.Text = string(runes[:CustomStatusTextMaxRunes])
}
}
func (cs *CustomStatus) ToJson() string {
csCopy := *cs
b, _ := json.Marshal(csCopy)
return string(b)
}
func (cs *CustomStatus) AreDurationAndExpirationTimeValid() bool {
if cs.Duration == "" && (cs.ExpiresAt.IsZero() || !cs.ExpiresAt.Before(time.Now())) {
return true
}
if validCustomStatusDuration[cs.Duration] && !cs.ExpiresAt.Before(time.Now()) {
return true
}
return false
}
func CustomStatusFromJson(data io.Reader) *CustomStatus {
var cs *CustomStatus
_ = json.NewDecoder(data).Decode(&cs)
return cs
}
func RuneToHexadecimalString(r rune) string {
return fmt.Sprintf("%04x", r)
}
type RecentCustomStatuses []CustomStatus
func (rcs *RecentCustomStatuses) Contains(cs *CustomStatus) bool {
var csJSON = cs.ToJson()
// status is empty
if cs == nil || csJSON == "" || (cs.Emoji == "" && cs.Text == "") {
return false
}
for _, status := range *rcs {
if status.ToJson() == csJSON {
return true
}
}
return false
}
func (rcs *RecentCustomStatuses) Add(cs *CustomStatus) *RecentCustomStatuses {
newRCS := (*rcs)[:0]
// if same `text` exists in existing recent custom statuses, modify existing status
for _, status := range *rcs {
if status.Text != cs.Text {
newRCS = append(newRCS, status)
}
}
newRCS = append(RecentCustomStatuses{*cs}, newRCS...)
if len(newRCS) > MaxRecentCustomStatuses {
newRCS = newRCS[:MaxRecentCustomStatuses]
}
return &newRCS
}
func (rcs *RecentCustomStatuses) Remove(cs *CustomStatus) *RecentCustomStatuses {
var csJSON = cs.ToJson()
if csJSON == "" || (cs.Emoji == "" && cs.Text == "") {
return rcs
}
newRCS := (*rcs)[:0]
for _, status := range *rcs {
if status.ToJson() != csJSON {
newRCS = append(newRCS, status)
}
}
return &newRCS
}
func (rcs *RecentCustomStatuses) ToJson() string {
rcsCopy := *rcs
b, _ := json.Marshal(rcsCopy)
return string(b)
}
func RecentCustomStatusesFromJson(data io.Reader) *RecentCustomStatuses {
var rcs *RecentCustomStatuses
_ = json.NewDecoder(data).Decode(&rcs)
return rcs
}

View File

@ -8,20 +8,125 @@ import (
"io"
)
type DataRetentionPolicy struct {
type GlobalRetentionPolicy struct {
MessageDeletionEnabled bool `json:"message_deletion_enabled"`
FileDeletionEnabled bool `json:"file_deletion_enabled"`
MessageRetentionCutoff int64 `json:"message_retention_cutoff"`
FileRetentionCutoff int64 `json:"file_retention_cutoff"`
}
func (me *DataRetentionPolicy) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
type RetentionPolicy struct {
ID string `db:"Id" json:"id"`
DisplayName string `json:"display_name"`
PostDuration *int64 `json:"post_duration"`
}
func DataRetentionPolicyFromJson(data io.Reader) *DataRetentionPolicy {
var me *DataRetentionPolicy
json.NewDecoder(data).Decode(&me)
return me
type RetentionPolicyWithTeamAndChannelIDs struct {
RetentionPolicy
TeamIDs []string `json:"team_ids"`
ChannelIDs []string `json:"channel_ids"`
}
type RetentionPolicyWithTeamAndChannelCounts struct {
RetentionPolicy
ChannelCount int64 `json:"channel_count"`
TeamCount int64 `json:"team_count"`
}
type RetentionPolicyChannel struct {
PolicyID string `db:"PolicyId"`
ChannelID string `db:"ChannelId"`
}
type RetentionPolicyTeam struct {
PolicyID string `db:"PolicyId"`
TeamID string `db:"TeamId"`
}
type RetentionPolicyWithTeamAndChannelCountsList struct {
Policies []*RetentionPolicyWithTeamAndChannelCounts `json:"policies"`
TotalCount int64 `json:"total_count"`
}
type RetentionPolicyForTeam struct {
TeamID string `db:"Id" json:"team_id"`
PostDuration int64 `json:"post_duration"`
}
type RetentionPolicyForTeamList struct {
Policies []*RetentionPolicyForTeam `json:"policies"`
TotalCount int64 `json:"total_count"`
}
type RetentionPolicyForChannel struct {
ChannelID string `db:"Id" json:"channel_id"`
PostDuration int64 `json:"post_duration"`
}
type RetentionPolicyForChannelList struct {
Policies []*RetentionPolicyForChannel `json:"policies"`
TotalCount int64 `json:"total_count"`
}
type RetentionPolicyCursor struct {
ChannelPoliciesDone bool
TeamPoliciesDone bool
GlobalPoliciesDone bool
}
func (rp *GlobalRetentionPolicy) ToJson() []byte {
b, _ := json.Marshal(rp)
return b
}
func GlobalRetentionPolicyFromJson(data io.Reader) *GlobalRetentionPolicy {
var grp *GlobalRetentionPolicy
json.NewDecoder(data).Decode(&grp)
return grp
}
func RetentionPolicyWithTeamAndChannelCountsFromJson(data io.Reader) (*RetentionPolicyWithTeamAndChannelCounts, error) {
var rp RetentionPolicyWithTeamAndChannelCounts
err := json.NewDecoder(data).Decode(&rp)
return &rp, err
}
func (rp *RetentionPolicyWithTeamAndChannelCounts) ToJson() []byte {
b, _ := json.Marshal(rp)
return b
}
func RetentionPolicyWithTeamAndChannelCountsListFromJson(data io.Reader) (*RetentionPolicyWithTeamAndChannelCountsList, error) {
var rpList *RetentionPolicyWithTeamAndChannelCountsList
err := json.NewDecoder(data).Decode(&rpList)
if err != nil {
return nil, err
}
return rpList, nil
}
func (rpList *RetentionPolicyWithTeamAndChannelCountsList) ToJson() []byte {
b, _ := json.Marshal(rpList)
return b
}
func RetentionPolicyWithTeamAndChannelIdsFromJson(data io.Reader) (*RetentionPolicyWithTeamAndChannelIDs, error) {
var rp *RetentionPolicyWithTeamAndChannelIDs
err := json.NewDecoder(data).Decode(&rp)
return rp, err
}
func (rp *RetentionPolicyWithTeamAndChannelIDs) ToJson() []byte {
b, _ := json.Marshal(rp)
return b
}
func (lst *RetentionPolicyForTeamList) ToJson() []byte {
b, _ := json.Marshal(lst)
return b
}
func (lst *RetentionPolicyForChannelList) ToJson() []byte {
b, _ := json.Marshal(lst)
return b
}

View File

@ -8,6 +8,7 @@ import (
"io"
"net/http"
"regexp"
"sort"
)
const (
@ -15,7 +16,9 @@ const (
EMOJI_SORT_BY_NAME = "name"
)
var EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_-]+:`)
var EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_+-]+:`)
var ReverseSystemEmojisMap = makeReverseEmojiMap()
type Emoji struct {
Id string `json:"id"`
@ -36,6 +39,26 @@ func GetSystemEmojiId(emojiName string) (string, bool) {
return id, found
}
func makeReverseEmojiMap() map[string][]string {
reverseEmojiMap := make(map[string][]string)
for key, value := range SystemEmojis {
emojiNames := reverseEmojiMap[value]
emojiNames = append(emojiNames, key)
sort.Strings(emojiNames)
reverseEmojiMap[value] = emojiNames
}
return reverseEmojiMap
}
func GetEmojiNameFromUnicode(unicode string) (emojiName string, count int) {
if emojiNames, found := ReverseSystemEmojisMap[unicode]; found {
return emojiNames[0], len(emojiNames)
}
return "", 0
}
func (emoji *Emoji) IsValid() *AppError {
if !IsValidId(emoji.Id) {
return NewAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "", http.StatusBadRequest)
@ -57,7 +80,7 @@ func (emoji *Emoji) IsValid() *AppError {
}
func IsValidEmojiName(name string) *AppError {
if len(name) == 0 || len(name) > EMOJI_NAME_MAX_LENGTH || !IsValidAlphaNumHyphenUnderscore(name, false) || inSystemEmoji(name) {
if name == "" || len(name) > EMOJI_NAME_MAX_LENGTH || !IsValidAlphaNumHyphenUnderscorePlus(name) || inSystemEmoji(name) {
return NewAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "", http.StatusBadRequest)
}

File diff suppressed because one or more lines are too long

View File

@ -3,16 +3,92 @@
package model
import (
"reflect"
"strconv"
)
type FeatureFlags struct {
// Exists only for unit and manual testing.
// When set to a value, will be returned by the ping endpoint.
TestFeature string
// Exists only for testing bool functionality. Boolean feature flags interpret "on" or "true" as true and
// all other values as false.
TestBoolFeature bool
// Toggle on and off scheduled jobs for cloud user limit emails see MM-29999
CloudDelinquentEmailJobsEnabled bool
// Toggle on and off support for Collapsed Threads
CollapsedThreads bool
// Enable the remote cluster service for shared channels.
EnableRemoteClusterService bool
// AppsEnabled toggle the Apps framework functionalities both in server and client side
AppsEnabled bool
// Feature flags to control plugin versions
PluginIncidentManagement string `plugin_id:"com.mattermost.plugin-incident-management"`
PluginApps string `plugin_id:"com.mattermost.apps"`
PluginFocalboard string `plugin_id:"focalboard"`
// Enable timed dnd support for user status
TimedDND bool
}
func (f *FeatureFlags) SetDefaults() {
f.TestFeature = "off"
f.TestBoolFeature = false
f.CloudDelinquentEmailJobsEnabled = false
f.CollapsedThreads = true
f.EnableRemoteClusterService = false
f.AppsEnabled = false
f.PluginIncidentManagement = "1.16.1"
f.PluginApps = ""
f.PluginFocalboard = ""
f.TimedDND = false
}
func (f *FeatureFlags) Plugins() map[string]string {
rFFVal := reflect.ValueOf(f).Elem()
rFFType := reflect.TypeOf(f).Elem()
pluginVersions := make(map[string]string)
for i := 0; i < rFFVal.NumField(); i++ {
rFieldVal := rFFVal.Field(i)
rFieldType := rFFType.Field(i)
pluginId, hasPluginId := rFieldType.Tag.Lookup("plugin_id")
if !hasPluginId {
continue
}
pluginVersions[pluginId] = rFieldVal.String()
}
return pluginVersions
}
// ToMap returns the feature flags as a map[string]string
// Supports boolean and string feature flags.
func (f *FeatureFlags) ToMap() map[string]string {
refStructVal := reflect.ValueOf(*f)
refStructType := reflect.TypeOf(*f)
ret := make(map[string]string)
for i := 0; i < refStructVal.NumField(); i++ {
refFieldVal := refStructVal.Field(i)
if !refFieldVal.IsValid() {
continue
}
refFieldType := refStructType.Field(i)
switch refFieldType.Type.Kind() {
case reflect.Bool:
ret[refFieldType.Name] = strconv.FormatBool(refFieldVal.Bool())
default:
ret[refFieldType.Name] = refFieldVal.String()
}
}
return ret
}

View File

@ -12,11 +12,6 @@ const (
MaxImageSize = int64(6048 * 4032) // 24 megapixels, roughly 36MB as a raw image
)
var (
IMAGE_EXTENSIONS = [7]string{".jpg", ".jpeg", ".gif", ".bmp", ".png", ".tiff", "tif"}
IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff", ".tif": "image/tif"}
)
type FileUploadResponse struct {
FileInfos []*FileInfo `json:"file_infos"`
ClientIds []string `json:"client_ids"`

View File

@ -4,19 +4,14 @@
package model
import (
"bytes"
"encoding/json"
"image"
"image/gif"
"image/jpeg"
"io"
"mime"
"net/http"
"path/filepath"
"strings"
"github.com/disintegration/imaging"
"github.com/mattermost/mattermost-server/v5/mlog"
)
const (
@ -44,6 +39,7 @@ type FileInfo struct {
Id string `json:"id"`
CreatorId string `json:"user_id"`
PostId string `json:"post_id,omitempty"`
ChannelId string `db:"-" json:"channel_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
@ -59,6 +55,7 @@ type FileInfo struct {
HasPreviewImage bool `json:"has_preview_image,omitempty"`
MiniPreview *[]byte `json:"mini_preview"` // declared as *[]byte to avoid postgres/mysql differences in deserialization
Content string `json:"-"`
RemoteId *string `json:"remote_id"`
}
func (fi *FileInfo) ToJson() string {
@ -72,9 +69,8 @@ func FileInfoFromJson(data io.Reader) *FileInfo {
var fi FileInfo
if err := decoder.Decode(&fi); err != nil {
return nil
} else {
return &fi
}
return &fi
}
func FileInfosToJson(infos []*FileInfo) string {
@ -88,9 +84,8 @@ func FileInfosFromJson(data io.Reader) []*FileInfo {
var infos []*FileInfo
if err := decoder.Decode(&infos); err != nil {
return nil
} else {
return infos
}
return infos
}
func (fi *FileInfo) PreSave() {
@ -105,6 +100,10 @@ func (fi *FileInfo) PreSave() {
if fi.UpdateAt < fi.CreateAt {
fi.UpdateAt = fi.CreateAt
}
if fi.RemoteId == nil {
fi.RemoteId = NewString("")
}
}
func (fi *FileInfo) IsValid() *AppError {
@ -116,7 +115,7 @@ func (fi *FileInfo) IsValid() *AppError {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
}
if len(fi.PostId) != 0 && !IsValidId(fi.PostId) {
if fi.PostId != "" && !IsValidId(fi.PostId) {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
}
@ -157,19 +156,6 @@ func NewInfo(name string) *FileInfo {
return info
}
func GenerateMiniPreviewImage(img image.Image) *[]byte {
preview := imaging.Resize(img, 16, 16, imaging.Lanczos)
buf := new(bytes.Buffer)
if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil {
mlog.Error("Unable to encode image as mini preview jpg", mlog.Err(err))
return nil
}
data := buf.Bytes()
return &data
}
func GetInfoForBytes(name string, data io.ReadSeeker, size int) (*FileInfo, *AppError) {
info := &FileInfo{
Name: name,
@ -196,13 +182,13 @@ func GetInfoForBytes(name string, data io.ReadSeeker, size int) (*FileInfo, *App
if info.MimeType == "image/gif" {
// Just show the gif itself instead of a preview image for animated gifs
data.Seek(0, io.SeekStart)
if gifConfig, err := gif.DecodeAll(data); err != nil {
gifConfig, err := gif.DecodeAll(data)
if err != nil {
// Still return the rest of the info even though it doesn't appear to be an actual gif
info.HasPreviewImage = true
return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, err.Error(), http.StatusBadRequest)
} else {
info.HasPreviewImage = len(gifConfig.Image) == 1
}
info.HasPreviewImage = len(gifConfig.Image) == 1
} else {
info.HasPreviewImage = true
}

View File

@ -0,0 +1,128 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"sort"
)
type FileInfoList struct {
Order []string `json:"order"`
FileInfos map[string]*FileInfo `json:"file_infos"`
NextFileInfoId string `json:"next_file_info_id"`
PrevFileInfoId string `json:"prev_file_info_id"`
}
func NewFileInfoList() *FileInfoList {
return &FileInfoList{
Order: make([]string, 0),
FileInfos: make(map[string]*FileInfo),
NextFileInfoId: "",
PrevFileInfoId: "",
}
}
func (o *FileInfoList) ToSlice() []*FileInfo {
var fileInfos []*FileInfo
for _, id := range o.Order {
fileInfos = append(fileInfos, o.FileInfos[id])
}
return fileInfos
}
func (o *FileInfoList) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func (o *FileInfoList) MakeNonNil() {
if o.Order == nil {
o.Order = make([]string, 0)
}
if o.FileInfos == nil {
o.FileInfos = make(map[string]*FileInfo)
}
}
func (o *FileInfoList) AddOrder(id string) {
if o.Order == nil {
o.Order = make([]string, 0, 128)
}
o.Order = append(o.Order, id)
}
func (o *FileInfoList) AddFileInfo(fileInfo *FileInfo) {
if o.FileInfos == nil {
o.FileInfos = make(map[string]*FileInfo)
}
o.FileInfos[fileInfo.Id] = fileInfo
}
func (o *FileInfoList) UniqueOrder() {
keys := make(map[string]bool)
order := []string{}
for _, fileInfoId := range o.Order {
if _, value := keys[fileInfoId]; !value {
keys[fileInfoId] = true
order = append(order, fileInfoId)
}
}
o.Order = order
}
func (o *FileInfoList) Extend(other *FileInfoList) {
for fileInfoId := range other.FileInfos {
o.AddFileInfo(other.FileInfos[fileInfoId])
}
for _, fileInfoId := range other.Order {
o.AddOrder(fileInfoId)
}
o.UniqueOrder()
}
func (o *FileInfoList) SortByCreateAt() {
sort.Slice(o.Order, func(i, j int) bool {
return o.FileInfos[o.Order[i]].CreateAt > o.FileInfos[o.Order[j]].CreateAt
})
}
func (o *FileInfoList) Etag() string {
id := "0"
var t int64 = 0
for _, v := range o.FileInfos {
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
} else if v.UpdateAt == t && v.Id > id {
t = v.UpdateAt
id = v.Id
}
}
orderId := ""
if len(o.Order) > 0 {
orderId = o.Order[0]
}
return Etag(orderId, id, t)
}
func FileInfoListFromJson(data io.Reader) *FileInfoList {
var o *FileInfoList
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type FileInfoSearchMatches map[string][]string
type FileInfoSearchResults struct {
*FileInfoList
Matches FileInfoSearchMatches `json:"matches"`
}
func MakeFileInfoSearchResults(fileInfos *FileInfoList, matches FileInfoSearchMatches) *FileInfoSearchResults {
return &FileInfoSearchResults{
fileInfos,
matches,
}
}
func (o *FileInfoSearchResults) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func FileInfoSearchResultsFromJson(data io.Reader) *FileInfoSearchResults {
var o *FileInfoSearchResults
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -139,7 +139,7 @@ func (group *Group) IsValidForCreate() *AppError {
return NewAppError("Group.IsValidForCreate", "model.group.source.app_error", nil, "", http.StatusBadRequest)
}
if len(group.RemoteId) > GroupRemoteIDMaxLength || (len(group.RemoteId) == 0 && group.requiresRemoteId()) {
if len(group.RemoteId) > GroupRemoteIDMaxLength || (group.RemoteId == "" && group.requiresRemoteId()) {
return NewAppError("Group.IsValidForCreate", "model.group.remote_id.app_error", nil, "", http.StatusBadRequest)
}

View File

@ -60,14 +60,14 @@ func (syncable *GroupSyncable) UnmarshalJSON(b []byte) error {
if err != nil {
return err
}
var channelId string
var teamId string
for key, value := range kvp {
switch key {
case "team_id":
syncable.SyncableId = value.(string)
syncable.Type = GroupSyncableTypeTeam
teamId = value.(string)
case "channel_id":
syncable.SyncableId = value.(string)
syncable.Type = GroupSyncableTypeChannel
channelId = value.(string)
case "group_id":
syncable.GroupId = value.(string)
case "auto_add":
@ -75,30 +75,40 @@ func (syncable *GroupSyncable) UnmarshalJSON(b []byte) error {
default:
}
}
if channelId != "" {
syncable.TeamID = teamId
syncable.SyncableId = channelId
syncable.Type = GroupSyncableTypeChannel
} else {
syncable.SyncableId = teamId
syncable.Type = GroupSyncableTypeTeam
}
return nil
}
func (syncable *GroupSyncable) MarshalJSON() ([]byte, error) {
type Alias GroupSyncable
switch syncable.Type {
case GroupSyncableTypeTeam:
return json.Marshal(&struct {
TeamID string `json:"team_id"`
TeamDisplayName string `json:"team_display_name,omitempty"`
TeamType string `json:"team_type,omitempty"`
TeamID string `json:"team_id"`
TeamDisplayName string `json:"team_display_name,omitempty"`
TeamType string `json:"team_type,omitempty"`
Type GroupSyncableType `json:"type,omitempty"`
*Alias
}{
TeamDisplayName: syncable.TeamDisplayName,
TeamType: syncable.TeamType,
TeamID: syncable.SyncableId,
Type: syncable.Type,
Alias: (*Alias)(syncable),
})
case GroupSyncableTypeChannel:
return json.Marshal(&struct {
ChannelID string `json:"channel_id"`
ChannelDisplayName string `json:"channel_display_name,omitempty"`
ChannelType string `json:"channel_type,omitempty"`
ChannelID string `json:"channel_id"`
ChannelDisplayName string `json:"channel_display_name,omitempty"`
ChannelType string `json:"channel_type,omitempty"`
Type GroupSyncableType `json:"type,omitempty"`
TeamID string `json:"team_id,omitempty"`
TeamDisplayName string `json:"team_display_name,omitempty"`
@ -109,6 +119,7 @@ func (syncable *GroupSyncable) MarshalJSON() ([]byte, error) {
ChannelID: syncable.SyncableId,
ChannelDisplayName: syncable.ChannelDisplayName,
ChannelType: syncable.ChannelType,
Type: syncable.Type,
TeamID: syncable.TeamID,
TeamDisplayName: syncable.TeamDisplayName,

View File

@ -23,7 +23,7 @@ func (i *GuestsInvite) IsValid() *AppError {
}
for _, email := range i.Emails {
if len(email) > USER_EMAIL_MAX_LENGTH || len(email) == 0 || !IsValidEmail(email) {
if len(email) > USER_EMAIL_MAX_LENGTH || email == "" || !IsValidEmail(email) {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.email.app_error", nil, "email="+email, http.StatusBadRequest)
}
}

View File

@ -182,9 +182,8 @@ func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) {
err := decoder.Decode(&o)
if err == nil {
return &o, nil
} else {
return nil, err
}
return nil, err
}
func IncomingWebhookRequestFromJson(data io.Reader) (*IncomingWebhookRequest, *AppError) {
@ -211,7 +210,6 @@ func (o *IncomingWebhookRequest) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
return string(b)
}

View File

@ -18,13 +18,13 @@ type InitialLoad struct {
NoAccounts bool `json:"no_accounts"`
}
func (me *InitialLoad) ToJson() string {
b, _ := json.Marshal(me)
func (il *InitialLoad) ToJson() string {
b, _ := json.Marshal(il)
return string(b)
}
func InitialLoadFromJson(data io.Reader) *InitialLoad {
var o *InitialLoad
json.NewDecoder(data).Decode(&o)
return o
var il *InitialLoad
json.NewDecoder(data).Decode(&il)
return il
}

View File

@ -394,7 +394,7 @@ func (o *Post) StripActionIntegrations() {
func (o *Post) GetAction(id string) *PostAction {
for _, attachment := range o.Attachments() {
for _, action := range attachment.Actions {
if action.Id == id {
if action != nil && action.Id == id {
return action
}
}
@ -409,7 +409,7 @@ func (o *Post) GenerateActionIds() {
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
for _, attachment := range attachments {
for _, action := range attachment.Actions {
if action.Id == "" {
if action != nil && action.Id == "" {
action.Id = NewId()
}
}

View File

@ -22,7 +22,12 @@ const (
JOB_TYPE_EXPIRY_NOTIFY = "expiry_notify"
JOB_TYPE_PRODUCT_NOTICES = "product_notices"
JOB_TYPE_ACTIVE_USERS = "active_users"
JOB_TYPE_IMPORT_PROCESS = "import_process"
JOB_TYPE_IMPORT_DELETE = "import_delete"
JOB_TYPE_EXPORT_PROCESS = "export_process"
JOB_TYPE_EXPORT_DELETE = "export_delete"
JOB_TYPE_CLOUD = "cloud"
JOB_TYPE_RESEND_INVITATION_EMAIL = "resend_invitation_email"
JOB_STATUS_PENDING = "pending"
JOB_STATUS_IN_PROGRESS = "in_progress"
@ -33,6 +38,25 @@ const (
JOB_STATUS_WARNING = "warning"
)
var ALL_JOB_TYPES = [...]string{
JOB_TYPE_DATA_RETENTION,
JOB_TYPE_MESSAGE_EXPORT,
JOB_TYPE_ELASTICSEARCH_POST_INDEXING,
JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION,
JOB_TYPE_BLEVE_POST_INDEXING,
JOB_TYPE_LDAP_SYNC,
JOB_TYPE_MIGRATIONS,
JOB_TYPE_PLUGINS,
JOB_TYPE_EXPIRY_NOTIFY,
JOB_TYPE_PRODUCT_NOTICES,
JOB_TYPE_ACTIVE_USERS,
JOB_TYPE_IMPORT_PROCESS,
JOB_TYPE_IMPORT_DELETE,
JOB_TYPE_EXPORT_PROCESS,
JOB_TYPE_EXPORT_DELETE,
JOB_TYPE_CLOUD,
}
type Job struct {
Id string `json:"id"`
Type string `json:"type"`
@ -66,7 +90,12 @@ func (j *Job) IsValid() *AppError {
case JOB_TYPE_PRODUCT_NOTICES:
case JOB_TYPE_EXPIRY_NOTIFY:
case JOB_TYPE_ACTIVE_USERS:
case JOB_TYPE_IMPORT_PROCESS:
case JOB_TYPE_IMPORT_DELETE:
case JOB_TYPE_EXPORT_PROCESS:
case JOB_TYPE_EXPORT_DELETE:
case JOB_TYPE_CLOUD:
case JOB_TYPE_RESEND_INVITATION_EMAIL:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
@ -94,9 +123,8 @@ func JobFromJson(data io.Reader) *Job {
var job Job
if err := json.NewDecoder(data).Decode(&job); err == nil {
return &job
} else {
return nil
}
return nil
}
func JobsToJson(jobs []*Job) string {
@ -108,9 +136,8 @@ func JobsFromJson(data io.Reader) []*Job {
var jobs []*Job
if err := json.NewDecoder(data).Decode(&jobs); err == nil {
return jobs
} else {
return nil
}
return nil
}
func (j *Job) DataToJson() string {

View File

@ -5,8 +5,10 @@ package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const (
@ -16,6 +18,22 @@ const (
LICENSE_RENEWAL_LINK = "https://mattermost.com/renew/"
)
const (
SIXTY_DAYS = 60
FIFTY_EIGHT = 58
LICENSE_UP_FOR_RENEWAL_EMAIL_SENT = "LicenseUpForRenewalEmailSent"
)
var (
trialDuration = 30*(time.Hour*24) + (time.Hour * 8) // 720 hours (30 days) + 8 hours is trial license duration
adminTrialDuration = 30*(time.Hour*24) + (time.Hour * 23) + (time.Minute * 59) + (time.Second * 59) // 720 hours (30 days) + 23 hours, 59 mins and 59 seconds
// a sanctioned trial's duration is either more than the upper bound,
// or less than the lower bound
sanctionedTrialDurationLowerBound = 31*(time.Hour*24) + (time.Hour * 23) + (time.Minute * 59) + (time.Second * 59) // 744 hours (31 days) + 23 hours, 59 mins and 59 seconds
sanctionedTrialDurationUpperBound = 29*(time.Hour*24) + (time.Hour * 23) + (time.Minute * 59) + (time.Second * 59) // 696 hours (29 days) + 23 hours, 59 mins and 59 seconds
)
type LicenseRecord struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
@ -31,6 +49,7 @@ type License struct {
Features *Features `json:"features"`
SkuName string `json:"sku_name"`
SkuShortName string `json:"sku_short_name"`
IsTrial bool `json:"is_trial"`
}
type Customer struct {
@ -63,6 +82,7 @@ type Features struct {
MFA *bool `json:"mfa"`
GoogleOAuth *bool `json:"google_oauth"`
Office365OAuth *bool `json:"office365_oauth"`
OpenId *bool `json:"openid"`
Compliance *bool `json:"compliance"`
Cluster *bool `json:"cluster"`
Metrics *bool `json:"metrics"`
@ -83,6 +103,8 @@ type Features struct {
EnterprisePlugins *bool `json:"enterprise_plugins"`
AdvancedLogging *bool `json:"advanced_logging"`
Cloud *bool `json:"cloud"`
SharedChannels *bool `json:"shared_channels"`
RemoteClusterService *bool `json:"remote_cluster_service"`
// after we enabled more features we'll need to control them with this
FutureFeatures *bool `json:"future_features"`
@ -95,6 +117,7 @@ func (f *Features) ToMap() map[string]interface{} {
"mfa": *f.MFA,
"google": *f.GoogleOAuth,
"office365": *f.Office365OAuth,
"openid": *f.OpenId,
"compliance": *f.Compliance,
"cluster": *f.Cluster,
"metrics": *f.Metrics,
@ -112,6 +135,8 @@ func (f *Features) ToMap() map[string]interface{} {
"enterprise_plugins": *f.EnterprisePlugins,
"advanced_logging": *f.AdvancedLogging,
"cloud": *f.Cloud,
"shared_channels": *f.SharedChannels,
"remote_cluster_service": *f.RemoteClusterService,
"future": *f.FutureFeatures,
}
}
@ -145,6 +170,10 @@ func (f *Features) SetDefaults() {
f.Office365OAuth = NewBool(*f.FutureFeatures)
}
if f.OpenId == nil {
f.OpenId = NewBool(*f.FutureFeatures)
}
if f.Compliance == nil {
f.Compliance = NewBool(*f.FutureFeatures)
}
@ -224,6 +253,14 @@ func (f *Features) SetDefaults() {
if f.Cloud == nil {
f.Cloud = NewBool(false)
}
if f.SharedChannels == nil {
f.SharedChannels = NewBool(*f.FutureFeatures)
}
if f.RemoteClusterService == nil {
f.RemoteClusterService = NewBool(*f.FutureFeatures)
}
}
func (l *License) IsExpired() bool {
@ -235,6 +272,18 @@ func (l *License) IsPastGracePeriod() bool {
return timeDiff > LICENSE_GRACE_PERIOD
}
func (l *License) IsWithinExpirationPeriod() bool {
days := l.DaysToExpiration()
return days <= SIXTY_DAYS && days >= FIFTY_EIGHT
}
func (l *License) DaysToExpiration() int {
dif := l.ExpiresAt - GetMillis()
d, _ := time.ParseDuration(fmt.Sprint(dif) + "ms")
days := d.Hours() / 24
return int(days)
}
func (l *License) IsStarted() bool {
return l.StartsAt < GetMillis()
}
@ -244,6 +293,17 @@ func (l *License) ToJson() string {
return string(b)
}
func (l *License) IsTrialLicense() bool {
return l.IsTrial || (l.ExpiresAt-l.StartsAt) == trialDuration.Milliseconds() || (l.ExpiresAt-l.StartsAt) == adminTrialDuration.Milliseconds()
}
func (l *License) IsSanctionedTrial() bool {
duration := l.ExpiresAt - l.StartsAt
return l.IsTrialLicense() &&
(duration >= sanctionedTrialDurationLowerBound.Milliseconds() || duration <= sanctionedTrialDurationUpperBound.Milliseconds())
}
// NewTestLicense returns a license that expires in the future and has the given features.
func NewTestLicense(features ...string) *License {
ret := &License{
@ -278,7 +338,7 @@ func (lr *LicenseRecord) IsValid() *AppError {
return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if len(lr.Bytes) == 0 || len(lr.Bytes) > 10000 {
if lr.Bytes == "" || len(lr.Bytes) > 10000 {
return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}

View File

@ -147,7 +147,7 @@ type Manifest struct {
Id string `json:"id" yaml:"id"`
// The name to be displayed for the plugin.
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Name string `json:"name" yaml:"name"`
// A description of what your plugin is and does.
Description string `json:"description,omitempty" yaml:"description,omitempty"`
@ -196,9 +196,21 @@ type Manifest struct {
}
type ManifestServer struct {
// Executables are the paths to your executable binaries, specifying multiple entry points
// for different platforms when bundled together in a single plugin.
Executables *ManifestExecutables `json:"executables,omitempty" yaml:"executables,omitempty"`
// AllExecutables are the paths to your executable binaries, specifying multiple entry
// points for different platforms when bundled together in a single plugin.
AllExecutables map[string]string `json:"executables,omitempty" yaml:"executables,omitempty"`
// Executables is a legacy field populated with a subset of supported platform executables.
// When unmarshalling, Executables is authoritative for the platform executable paths it
// contains, overriding any values in AllExecutables. When marshalling, AllExecutables
// is authoritative.
//
// Code duplication is avoided when (un)marshalling by leveraging type aliases in the
// various (Un)Marshal(JSON|YAML) methods, since aliases don't inherit the aliased type's
// methods.
//
// In v6.0, we should remove this field and rename AllExecutables back to Executables.
Executables *ManifestExecutables `json:"-" yaml:"-"`
// Executable is the path to your executable binary. This should be relative to the root
// of your bundle and the location of the manifest file.
@ -210,6 +222,79 @@ type ManifestServer struct {
Executable string `json:"executable" yaml:"executable"`
}
func (ms *ManifestServer) MarshalJSON() ([]byte, error) {
type auxManifestServer ManifestServer
// Populate AllExecutables from Executables, if it exists.
if ms.Executables != nil {
if ms.AllExecutables == nil {
ms.AllExecutables = make(map[string]string)
}
ms.AllExecutables["linux-amd64"] = ms.Executables.LinuxAmd64
ms.AllExecutables["darwin-amd64"] = ms.Executables.DarwinAmd64
ms.AllExecutables["windows-amd64"] = ms.Executables.WindowsAmd64
}
return json.Marshal((*auxManifestServer)(ms))
}
func (ms *ManifestServer) UnmarshalJSON(data []byte) error {
type auxManifestServer ManifestServer
aux := (*auxManifestServer)(ms)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
if len(aux.AllExecutables) > 0 {
ms.Executables = &ManifestExecutables{
LinuxAmd64: aux.AllExecutables["linux-amd64"],
DarwinAmd64: aux.AllExecutables["darwin-amd64"],
WindowsAmd64: aux.AllExecutables["windows-amd64"],
}
}
return nil
}
func (ms *ManifestServer) MarshalYAML() ([]byte, error) {
type auxManifestServer ManifestServer
// Populate AllExecutables from Executables, if it exists.
if ms.Executables != nil {
if ms.AllExecutables == nil {
ms.AllExecutables = make(map[string]string)
}
ms.AllExecutables["linux-amd64"] = ms.Executables.LinuxAmd64
ms.AllExecutables["darwin-amd64"] = ms.Executables.DarwinAmd64
ms.AllExecutables["windows-amd64"] = ms.Executables.WindowsAmd64
}
return yaml.Marshal((*auxManifestServer)(ms))
}
func (ms *ManifestServer) UnmarshalYAML(unmarshal func(interface{}) error) error {
type auxManifestServer ManifestServer
aux := (*auxManifestServer)(ms)
if err := unmarshal(&aux); err != nil {
return err
}
if len(aux.AllExecutables) > 0 {
ms.Executables = &ManifestExecutables{
LinuxAmd64: aux.AllExecutables["linux-amd64"],
DarwinAmd64: aux.AllExecutables["darwin-amd64"],
WindowsAmd64: aux.AllExecutables["windows-amd64"],
}
}
return nil
}
// ManifestExecutables is a legacy structure capturing a subet of the known platform executables.
type ManifestExecutables struct {
// LinuxAmd64 is the path to your executable binary for the corresponding platform
LinuxAmd64 string `json:"linux-amd64,omitempty" yaml:"linux-amd64,omitempty"`
@ -287,14 +372,9 @@ func (m *Manifest) GetExecutableForRuntime(goOs, goArch string) string {
}
var executable string
if server.Executables != nil {
if goOs == "linux" && goArch == "amd64" {
executable = server.Executables.LinuxAmd64
} else if goOs == "darwin" && goArch == "amd64" {
executable = server.Executables.DarwinAmd64
} else if goOs == "windows" && goArch == "amd64" {
executable = server.Executables.WindowsAmd64
}
if len(server.AllExecutables) > 0 {
osArch := fmt.Sprintf("%s-%s", goOs, goArch)
executable = server.AllExecutables[osArch]
}
if executable == "" {
@ -329,6 +409,10 @@ func (m *Manifest) IsValid() error {
return errors.New("invalid plugin ID")
}
if strings.TrimSpace(m.Name) == "" {
return errors.New("a plugin name is needed")
}
if m.HomepageURL != "" && !IsValidHttpUrl(m.HomepageURL) {
return errors.New("invalid HomepageURL")
}

View File

@ -20,8 +20,12 @@ type BaseMarketplacePlugin struct {
IconData string `json:"icon_data"`
DownloadURL string `json:"download_url"`
ReleaseNotesURL string `json:"release_notes_url"`
Labels []MarketplaceLabel `json:"labels"`
Signature string `json:"signature"` // Signature represents a signature of a plugin saved in base64 encoding.
Labels []MarketplaceLabel `json:"labels,omitempty"`
Hosting string `json:"hosting"` // Indicated if the plugin is limited to a certain hosting type
AuthorType string `json:"author_type"` // The maintainer of the plugin
ReleaseStage string `json:"release_stage"` // The stage in the software release cycle that the plugin is in
Enterprise bool `json:"enterprise"` // Indicated if the plugin is an enterprise plugin
Signature string `json:"signature"` // Signature represents a signature of a plugin saved in base64 encoding.
Manifest *Manifest `json:"manifest"`
}
@ -83,6 +87,8 @@ type MarketplacePluginFilter struct {
Cloud bool
LocalOnly bool
Platform string
PluginId string
ReturnAllVersions bool
}
// ApplyToURL modifies the given url to include query string parameters for the request.
@ -99,6 +105,8 @@ func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
q.Add("cloud", strconv.FormatBool(filter.Cloud))
q.Add("local_only", strconv.FormatBool(filter.LocalOnly))
q.Add("platform", filter.Platform)
q.Add("plugin_id", filter.PluginId)
q.Add("return_all_versions", strconv.FormatBool(filter.ReturnAllVersions))
u.RawQuery = q.Encode()
}

View File

@ -29,3 +29,8 @@ type MessageExport struct {
PostOriginalId *string
PostFileIds StringArray
}
type MessageExportCursor struct {
LastPostUpdateAt int64
LastPostId string
}

View File

@ -13,13 +13,13 @@ type MfaSecret struct {
QRCode string `json:"qr_code"`
}
func (me *MfaSecret) ToJson() string {
b, _ := json.Marshal(me)
func (mfa *MfaSecret) ToJson() string {
b, _ := json.Marshal(mfa)
return string(b)
}
func MfaSecretFromJson(data io.Reader) *MfaSecret {
var me *MfaSecret
json.NewDecoder(data).Decode(&me)
return me
var mfa *MfaSecret
json.NewDecoder(data).Decode(&mfa)
return mfa
}

View File

@ -22,5 +22,17 @@ const (
MIGRATION_KEY_SIDEBAR_CATEGORIES_PHASE_2 = "migration_sidebar_categories_phase_2"
MIGRATION_KEY_ADD_CONVERT_CHANNEL_PERMISSIONS = "add_convert_channel_permissions"
MIGRATION_KEY_ADD_SYSTEM_ROLES_PERMISSIONS = "add_system_roles_permissions"
MIGRATION_KEY_ADD_BILLING_PERMISSIONS = "add_billing_permissions"
MIGRATION_KEY_ADD_MANAGE_SHARED_CHANNEL_PERMISSIONS = "manage_shared_channel_permissions"
MIGRATION_KEY_ADD_MANAGE_SECURE_CONNECTIONS_PERMISSIONS = "manage_secure_connections_permissions"
MIGRATION_KEY_ADD_DOWNLOAD_COMPLIANCE_EXPORT_RESULTS = "download_compliance_export_results"
MIGRATION_KEY_ADD_COMPLIANCE_SUBSECTION_PERMISSIONS = "compliance_subsection_permissions"
MIGRATION_KEY_ADD_EXPERIMENTAL_SUBSECTION_PERMISSIONS = "experimental_subsection_permissions"
MIGRATION_KEY_ADD_AUTHENTICATION_SUBSECTION_PERMISSIONS = "authentication_subsection_permissions"
MIGRATION_KEY_ADD_SITE_SUBSECTION_PERMISSIONS = "site_subsection_permissions"
MIGRATION_KEY_ADD_ENVIRONMENT_SUBSECTION_PERMISSIONS = "environment_subsection_permissions"
MIGRATION_KEY_ADD_REPORTING_SUBSECTION_PERMISSIONS = "reporting_subsection_permissions"
MIGRATION_KEY_ADD_TEST_EMAIL_ANCILLARY_PERMISSION = "test_email_ancillary_permission"
MIGRATION_KEY_ADD_ABOUT_SUBSECTION_PERMISSIONS = "about_subsection_permissions"
MIGRATION_KEY_ADD_INTEGRATIONS_SUBSECTION_PERMISSIONS = "integrations_subsection_permissions"
)

View File

@ -53,11 +53,11 @@ func (a *OAuthApp) IsValid() *AppError {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
if a.ClientSecret == "" || len(a.ClientSecret) > 128 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.Name) == 0 || len(a.Name) > 64 {
if a.Name == "" || len(a.Name) > 64 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
@ -71,7 +71,7 @@ func (a *OAuthApp) IsValid() *AppError {
}
}
if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
if a.Homepage == "" || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
@ -79,7 +79,7 @@ func (a *OAuthApp) IsValid() *AppError {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.IconURL) > 0 {
if a.IconURL != "" {
if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}

View File

@ -140,7 +140,7 @@ func (o *OutgoingWebhook) IsValid() *AppError {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ChannelId) != 0 && !IsValidId(o.ChannelId) {
if o.ChannelId != "" && !IsValidId(o.ChannelId) {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
@ -154,7 +154,7 @@ func (o *OutgoingWebhook) IsValid() *AppError {
if len(o.TriggerWords) != 0 {
for _, triggerWord := range o.TriggerWords {
if len(triggerWord) == 0 {
if triggerWord == "" {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest)
}
}
@ -215,7 +215,7 @@ func (o *OutgoingWebhook) PreUpdate() {
}
func (o *OutgoingWebhook) TriggerWordExactMatch(word string) bool {
if len(word) == 0 {
if word == "" {
return false
}
@ -229,7 +229,7 @@ func (o *OutgoingWebhook) TriggerWordExactMatch(word string) bool {
}
func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
if len(word) == 0 {
if word == "" {
return false
}
@ -243,7 +243,7 @@ func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
}
func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) {
if len(word) == 0 {
if word == "" {
return
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
const (
PluginClusterEventSendTypeReliable = CLUSTER_SEND_RELIABLE
PluginClusterEventSendTypeBestEffort = CLUSTER_SEND_BEST_EFFORT
)
// PluginClusterEvent is used to allow intra-cluster plugin communication.
type PluginClusterEvent struct {
// Id is the unique identifier for the event.
Id string
// Data is the event payload.
Data []byte
}
// PluginClusterEventSendOptions defines some properties that apply when sending
// plugin events across a cluster.
type PluginClusterEventSendOptions struct {
// SendType defines the type of communication channel used to send the event.
SendType string
// TargetId identifies the cluster node to which the event should be sent.
// It should match the cluster id of the receiving instance.
// If empty, the event gets broadcasted to all other nodes.
TargetId string
}

View File

@ -21,11 +21,11 @@ type PluginKeyValue struct {
}
func (kv *PluginKeyValue) IsValid() *AppError {
if len(kv.PluginId) == 0 || utf8.RuneCountInString(kv.PluginId) > KEY_VALUE_PLUGIN_ID_MAX_RUNES {
if kv.PluginId == "" || utf8.RuneCountInString(kv.PluginId) > KEY_VALUE_PLUGIN_ID_MAX_RUNES {
return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.plugin_id.app_error", map[string]interface{}{"Max": KEY_VALUE_KEY_MAX_RUNES, "Min": 0}, "key="+kv.Key, http.StatusBadRequest)
}
if len(kv.Key) == 0 || utf8.RuneCountInString(kv.Key) > KEY_VALUE_KEY_MAX_RUNES {
if kv.Key == "" || utf8.RuneCountInString(kv.Key) > KEY_VALUE_KEY_MAX_RUNES {
return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.key.app_error", map[string]interface{}{"Max": KEY_VALUE_KEY_MAX_RUNES, "Min": 0}, "key="+kv.Key, http.StatusBadRequest)
}

View File

@ -14,7 +14,7 @@ import (
"sync"
"unicode/utf8"
"github.com/mattermost/mattermost-server/v5/utils/markdown"
"github.com/mattermost/mattermost-server/v5/shared/markdown"
)
const (
@ -45,7 +45,7 @@ const (
POST_EPHEMERAL = "system_ephemeral"
POST_CHANGE_CHANNEL_PRIVACY = "system_change_chan_privacy"
POST_ADD_BOT_TEAMS_CHANNELS = "add_bot_teams_channels"
POST_FILEIDS_MAX_RUNES = 150
POST_FILEIDS_MAX_RUNES = 300
POST_FILENAMES_MAX_RUNES = 4000
POST_HASHTAGS_MAX_RUNES = 1000
POST_MESSAGE_MAX_RUNES_V1 = 4000
@ -96,10 +96,14 @@ type Post struct {
FileIds StringArray `json:"file_ids,omitempty"`
PendingPostId string `json:"pending_post_id" db:"-"`
HasReactions bool `json:"has_reactions,omitempty"`
RemoteId *string `json:"remote_id,omitempty"`
// Transient data populated before sending a post to the client
ReplyCount int64 `json:"reply_count" db:"-"`
Metadata *PostMetadata `json:"metadata,omitempty" db:"-"`
ReplyCount int64 `json:"reply_count" db:"-"`
LastReplyAt int64 `json:"last_reply_at" db:"-"`
Participants []*User `json:"participants" db:"-"`
IsFollowing *bool `json:"is_following,omitempty" db:"-"` // for root posts in collapsed thread mode indicates if the current user is following this thread
Metadata *PostMetadata `json:"metadata,omitempty" db:"-"`
}
type PostEphemeral struct {
@ -163,6 +167,12 @@ type PostForIndexing struct {
ParentCreateAt *int64 `json:"parent_create_at"`
}
type FileForIndexing struct {
FileInfo
ChannelId string `json:"channel_id"`
Content string `json:"content"`
}
// ShallowCopy is an utility function to shallow copy a Post to the given
// destination without touching the internal RWMutex.
func (o *Post) ShallowCopy(dst *Post) error {
@ -194,7 +204,13 @@ func (o *Post) ShallowCopy(dst *Post) error {
dst.PendingPostId = o.PendingPostId
dst.HasReactions = o.HasReactions
dst.ReplyCount = o.ReplyCount
dst.Participants = o.Participants
dst.LastReplyAt = o.LastReplyAt
dst.Metadata = o.Metadata
if o.IsFollowing != nil {
dst.IsFollowing = NewBool(*o.IsFollowing)
}
dst.RemoteId = o.RemoteId
return nil
}
@ -218,17 +234,35 @@ func (o *Post) ToUnsanitizedJson() string {
}
type GetPostsSinceOptions struct {
ChannelId string
Time int64
SkipFetchThreads bool
UserId string
ChannelId string
Time int64
SkipFetchThreads bool
CollapsedThreads bool
CollapsedThreadsExtended bool
SortAscending bool
}
type GetPostsSinceForSyncCursor struct {
LastPostUpdateAt int64
LastPostId string
}
type GetPostsSinceForSyncOptions struct {
ChannelId string
ExcludeRemoteId string
IncludeDeleted bool
}
type GetPostsOptions struct {
ChannelId string
PostId string
Page int
PerPage int
SkipFetchThreads bool
UserId string
ChannelId string
PostId string
Page int
PerPage int
SkipFetchThreads bool
CollapsedThreads bool
CollapsedThreadsExtended bool
}
func PostFromJson(data io.Reader) *Post {
@ -262,19 +296,19 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
return NewAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if !(IsValidId(o.RootId) || len(o.RootId) == 0) {
if !(IsValidId(o.RootId) || o.RootId == "") {
return NewAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "", http.StatusBadRequest)
}
if !(IsValidId(o.ParentId) || len(o.ParentId) == 0) {
if !(IsValidId(o.ParentId) || o.ParentId == "") {
return NewAppError("Post.IsValid", "model.post.is_valid.parent_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ParentId) == 26 && len(o.RootId) == 0 {
if len(o.ParentId) == 26 && o.RootId == "" {
return NewAppError("Post.IsValid", "model.post.is_valid.root_parent.app_error", nil, "", http.StatusBadRequest)
}
if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) {
if !(len(o.OriginalId) == 26 || o.OriginalId == "") {
return NewAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "", http.StatusBadRequest)
}
@ -337,6 +371,9 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
}
func (o *Post) SanitizeProps() {
if o == nil {
return
}
membersToSanitize := []string{
PROPS_ADD_CHANNEL_MEMBER,
}
@ -346,6 +383,9 @@ func (o *Post) SanitizeProps() {
o.DelProp(member)
}
}
for _, p := range o.Participants {
p.Sanitize(map[string]bool{})
}
}
func (o *Post) PreSave() {
@ -432,6 +472,19 @@ 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
}
// IsRemote returns true if the post originated on a remote cluster.
func (o *Post) IsRemote() bool {
return o.RemoteId != nil && *o.RemoteId != ""
}
// GetRemoteID safely returns the remoteID or empty string if not remote.
func (o *Post) GetRemoteID() string {
if o.RemoteId != nil {
return *o.RemoteId
}
return ""
}
func (o *Post) IsJoinLeaveMessage() bool {
return o.Type == POST_JOIN_LEAVE ||
o.Type == POST_ADD_REMOVE ||
@ -552,6 +605,25 @@ func (o *Post) Attachments() []*SlackAttachment {
if enc, err := json.Marshal(attachment); err == nil {
var decoded SlackAttachment
if json.Unmarshal(enc, &decoded) == nil {
// Ignoring nil actions
i := 0
for _, action := range decoded.Actions {
if action != nil {
decoded.Actions[i] = action
i++
}
}
decoded.Actions = decoded.Actions[:i]
// Ignoring nil fields
i = 0
for _, field := range decoded.Fields {
if field != nil {
decoded.Fields[i] = field
i++
}
}
decoded.Fields = decoded.Fields[:i]
ret = append(ret, &decoded)
}
}
@ -665,3 +737,15 @@ func RewriteImageURLs(message string, f func(string) string) string {
return string(result)
}
func (o *Post) IsFromOAuthBot() bool {
props := o.GetProps()
return props["from_webhook"] == "true" && props["override_username"] != ""
}
func (o *Post) ToNilIfInvalid() *Post {
if o.Id == "" {
return nil
}
return o
}

View File

@ -27,6 +27,11 @@ func NewPostList() *PostList {
func (o *PostList) ToSlice() []*Post {
var posts []*Post
if l := len(o.Posts); l > 0 {
posts = make([]*Post, 0, l)
}
for _, id := range o.Order {
posts = append(posts, o.Posts[id])
}
@ -58,9 +63,8 @@ func (o *PostList) ToJson() string {
b, err := json.Marshal(&copy)
if err != nil {
return ""
} else {
return string(b)
}
return string(b)
}
func (o *PostList) MakeNonNil() {

View File

@ -28,9 +28,8 @@ func (o *PostSearchResults) ToJson() string {
b, err := json.Marshal(&copy)
if err != nil {
return ""
} else {
return string(b)
}
return string(b)
}
func PostSearchResultsFromJson(data io.Reader) *PostSearchResults {

View File

@ -21,12 +21,14 @@ const (
PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel"
PREFERENCE_CATEGORY_SIDEBAR_SETTINGS = "sidebar_settings"
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
PREFERENCE_NAME_CHANNEL_DISPLAY_MODE = "channel_display_mode"
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
PREFERENCE_NAME_MESSAGE_DISPLAY = "message_display"
PREFERENCE_NAME_NAME_FORMAT = "name_format"
PREFERENCE_NAME_USE_MILITARY_TIME = "use_military_time"
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
PREFERENCE_NAME_COLLAPSED_THREADS_ENABLED = "collapsed_reply_threads"
PREFERENCE_NAME_CHANNEL_DISPLAY_MODE = "channel_display_mode"
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
PREFERENCE_NAME_MESSAGE_DISPLAY = "message_display"
PREFERENCE_NAME_NAME_FORMAT = "name_format"
PREFERENCE_NAME_USE_MILITARY_TIME = "use_military_time"
PREFERENCE_RECOMMENDED_NEXT_STEPS = "recommended_next_steps"
PREFERENCE_CATEGORY_THEME = "theme"
// the name for theme props is the team id
@ -38,6 +40,12 @@ const (
PREFERENCE_NAME_LAST_CHANNEL = "channel"
PREFERENCE_NAME_LAST_TEAM = "team"
PREFERENCE_CATEGORY_CUSTOM_STATUS = "custom_status"
PREFERENCE_NAME_RECENT_CUSTOM_STATUSES = "recent_custom_statuses"
PREFERENCE_NAME_CUSTOM_STATUS_TUTORIAL_STATE = "custom_status_tutorial_state"
PREFERENCE_CUSTOM_STATUS_MODAL_VIEWED = "custom_status_modal_viewed"
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
@ -73,7 +81,7 @@ func (o *Preference) IsValid() *AppError {
return NewAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest)
}
if len(o.Category) == 0 || len(o.Category) > 32 {
if o.Category == "" || len(o.Category) > 32 {
return NewAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category, http.StatusBadRequest)
}

View File

@ -19,9 +19,8 @@ func PreferencesFromJson(data io.Reader) (Preferences, error) {
decoder := json.NewDecoder(data)
var o Preferences
err := decoder.Decode(&o)
if err == nil {
return o, nil
} else {
if err != nil {
return nil, err
}
return o, nil
}

View File

@ -5,8 +5,9 @@ package model
import (
"encoding/json"
"github.com/pkg/errors"
"io"
"github.com/pkg/errors"
)
type ProductNotices []ProductNotice
@ -39,18 +40,19 @@ func (n *ProductNotice) TeamAdminOnly() bool {
}
type Conditions struct {
Audience *NoticeAudience `json:"audience,omitempty"`
ClientType *NoticeClientType `json:"clientType,omitempty"` // Only show the notice on specific clients. Defaults to 'all'
DesktopVersion []string `json:"desktopVersion,omitempty"` // What desktop client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
DisplayDate *string `json:"displayDate,omitempty"` // When to display the notice.; Examples:; "2020-03-01T00:00:00Z" - show on specified date; ">= 2020-03-01T00:00:00Z" - show after specified date; "< 2020-03-01T00:00:00Z" - show before the specified date; "> 2020-03-01T00:00:00Z <= 2020-04-01T00:00:00Z" - show only between the specified dates
InstanceType *NoticeInstanceType `json:"instanceType,omitempty"`
MobileVersion []string `json:"mobileVersion,omitempty"` // What mobile client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
NumberOfPosts *int64 `json:"numberOfPosts,omitempty"` // Only show the notice when server has more than specified number of posts
NumberOfUsers *int64 `json:"numberOfUsers,omitempty"` // Only show the notice when server has more than specified number of users
ServerConfig map[string]interface{} `json:"serverConfig,omitempty"` // Map of mattermost server config paths and their values. Notice will be displayed only if; the values match the target server config; Example: serverConfig: { "PluginSettings.Enable": true, "GuestAccountsSettings.Enable":; false }
ServerVersion []string `json:"serverVersion,omitempty"` // What server versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
Sku *NoticeSKU `json:"sku,omitempty"`
UserConfig map[string]interface{} `json:"userConfig,omitempty"` // Map of user's settings and their values. Notice will be displayed only if the values; match the viewing users' config; Example: userConfig: { "new_sidebar.disabled": true }
Audience *NoticeAudience `json:"audience,omitempty"`
ClientType *NoticeClientType `json:"clientType,omitempty"` // Only show the notice on specific clients. Defaults to 'all'
DesktopVersion []string `json:"desktopVersion,omitempty"` // What desktop client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
DisplayDate *string `json:"displayDate,omitempty"` // When to display the notice.; Examples:; "2020-03-01T00:00:00Z" - show on specified date; ">= 2020-03-01T00:00:00Z" - show after specified date; "< 2020-03-01T00:00:00Z" - show before the specified date; "> 2020-03-01T00:00:00Z <= 2020-04-01T00:00:00Z" - show only between the specified dates
InstanceType *NoticeInstanceType `json:"instanceType,omitempty"`
MobileVersion []string `json:"mobileVersion,omitempty"` // What mobile client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
NumberOfPosts *int64 `json:"numberOfPosts,omitempty"` // Only show the notice when server has more than specified number of posts
NumberOfUsers *int64 `json:"numberOfUsers,omitempty"` // Only show the notice when server has more than specified number of users
ServerConfig map[string]interface{} `json:"serverConfig,omitempty"` // Map of mattermost server config paths and their values. Notice will be displayed only if; the values match the target server config; Example: serverConfig: { "PluginSettings.Enable": true, "GuestAccountsSettings.Enable":; false }
ServerVersion []string `json:"serverVersion,omitempty"` // What server versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
Sku *NoticeSKU `json:"sku,omitempty"`
UserConfig map[string]interface{} `json:"userConfig,omitempty"` // Map of user's settings and their values. Notice will be displayed only if the values; match the viewing users' config; Example: userConfig: { "new_sidebar.disabled": true }
DeprecatingDependency *ExternalDependency `json:"deprecating_dependency,omitempty"` // External dependency which is going to be deprecated
}
type NoticeMessageInternal struct {
@ -211,3 +213,8 @@ type ProductNoticeViewState struct {
Viewed int32
Timestamp int64
}
type ExternalDependency struct {
Name string `json:"name"`
MinimumVersion string `json:"minimum_version"`
}

View File

@ -70,23 +70,23 @@ type PushNotification struct {
IsIdLoaded bool `json:"is_id_loaded"`
}
func (me *PushNotification) ToJson() string {
b, _ := json.Marshal(me)
func (pn *PushNotification) ToJson() string {
b, _ := json.Marshal(pn)
return string(b)
}
func (me *PushNotification) DeepCopy() *PushNotification {
copy := *me
func (pn *PushNotification) DeepCopy() *PushNotification {
copy := *pn
return &copy
}
func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) {
func (pn *PushNotification) SetDeviceIdAndPlatform(deviceId string) {
index := strings.Index(deviceId, ":")
if index > -1 {
me.Platform = deviceId[:index]
me.DeviceId = deviceId[index+1:]
pn.Platform = deviceId[:index]
pn.DeviceId = deviceId[index+1:]
}
}
@ -94,11 +94,11 @@ func PushNotificationFromJson(data io.Reader) (*PushNotification, error) {
if data == nil {
return nil, errors.New("push notification data can't be nil")
}
var me *PushNotification
if err := json.NewDecoder(data).Decode(&me); err != nil {
var pn *PushNotification
if err := json.NewDecoder(data).Decode(&pn); err != nil {
return nil, err
}
return me, nil
return pn, nil
}
func PushNotificationAckFromJson(data io.Reader) (*PushNotificationAck, error) {

View File

@ -37,8 +37,8 @@ func NewErrorPushResponse(message string) PushResponse {
return m
}
func (me *PushResponse) ToJson() string {
b, _ := json.Marshal(me)
func (pr *PushResponse) ToJson() string {
b, _ := json.Marshal(pr)
return string(b)
}
@ -48,7 +48,6 @@ func PushResponseFromJson(data io.Reader) PushResponse {
var objmap PushResponse
if err := decoder.Decode(&objmap); err != nil {
return make(map[string]string)
} else {
return objmap
}
return objmap
}

View File

@ -11,10 +11,13 @@ import (
)
type Reaction struct {
UserId string `json:"user_id"`
PostId string `json:"post_id"`
EmojiName string `json:"emoji_name"`
CreateAt int64 `json:"create_at"`
UserId string `json:"user_id"`
PostId string `json:"post_id"`
EmojiName string `json:"emoji_name"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
RemoteId *string `json:"remote_id"`
}
func (o *Reaction) ToJson() string {
@ -27,9 +30,8 @@ func ReactionFromJson(data io.Reader) *Reaction {
if err := json.NewDecoder(data).Decode(&o); err != nil {
return nil
} else {
return &o
}
return &o
}
func ReactionsToJson(o []*Reaction) string {
@ -48,9 +50,8 @@ func MapPostIdToReactionsFromJson(data io.Reader) map[string][]*Reaction {
var objmap map[string][]*Reaction
if err := decoder.Decode(&objmap); err != nil {
return make(map[string][]*Reaction)
} else {
return objmap
}
return objmap
}
func ReactionsFromJson(data io.Reader) []*Reaction {
@ -58,9 +59,8 @@ func ReactionsFromJson(data io.Reader) []*Reaction {
if err := json.NewDecoder(data).Decode(&o); err != nil {
return nil
} else {
return o
}
return o
}
func (o *Reaction) IsValid() *AppError {
@ -74,7 +74,7 @@ func (o *Reaction) IsValid() *AppError {
validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`)
if len(o.EmojiName) == 0 || len(o.EmojiName) > EMOJI_NAME_MAX_LENGTH || !validName.MatchString(o.EmojiName) {
if o.EmojiName == "" || len(o.EmojiName) > EMOJI_NAME_MAX_LENGTH || !validName.MatchString(o.EmojiName) {
return NewAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName, http.StatusBadRequest)
}
@ -82,6 +82,10 @@ func (o *Reaction) IsValid() *AppError {
return NewAppError("Reaction.IsValid", "model.reaction.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("Reaction.IsValid", "model.reaction.is_valid.update_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
@ -89,4 +93,18 @@ func (o *Reaction) PreSave() {
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
}
o.UpdateAt = GetMillis()
o.DeleteAt = 0
if o.RemoteId == nil {
o.RemoteId = NewString("")
}
}
func (o *Reaction) PreUpdate() {
o.UpdateAt = GetMillis()
if o.RemoteId == nil {
o.RemoteId = NewString("")
}
}

View File

@ -0,0 +1,355 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"errors"
"io"
"net/http"
"regexp"
"strings"
"golang.org/x/crypto/scrypt"
)
const (
RemoteOfflineAfterMillis = 1000 * 60 * 5 // 5 minutes
RemoteNameMinLength = 1
RemoteNameMaxLength = 64
)
var (
validRemoteNameChars = regexp.MustCompile(`^[a-zA-Z0-9\.\-\_]+$`)
)
type RemoteCluster struct {
RemoteId string `json:"remote_id"`
RemoteTeamId string `json:"remote_team_id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
SiteURL string `json:"site_url"`
CreateAt int64 `json:"create_at"`
LastPingAt int64 `json:"last_ping_at"`
Token string `json:"token"`
RemoteToken string `json:"remote_token"`
Topics string `json:"topics"`
CreatorId string `json:"creator_id"`
}
func (rc *RemoteCluster) PreSave() {
if rc.RemoteId == "" {
rc.RemoteId = NewId()
}
if rc.DisplayName == "" {
rc.DisplayName = rc.Name
}
rc.Name = SanitizeUnicode(rc.Name)
rc.DisplayName = SanitizeUnicode(rc.DisplayName)
rc.Name = NormalizeRemoteName(rc.Name)
if rc.Token == "" {
rc.Token = NewId()
}
if rc.CreateAt == 0 {
rc.CreateAt = GetMillis()
}
rc.fixTopics()
}
func (rc *RemoteCluster) IsValid() *AppError {
if !IsValidId(rc.RemoteId) {
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "id="+rc.RemoteId, http.StatusBadRequest)
}
if !IsValidRemoteName(rc.Name) {
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.name.app_error", nil, "name="+rc.Name, http.StatusBadRequest)
}
if rc.CreateAt == 0 {
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.create_at.app_error", nil, "create_at=0", http.StatusBadRequest)
}
if !IsValidId(rc.CreatorId) {
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "creator_id="+rc.CreatorId, http.StatusBadRequest)
}
return nil
}
func IsValidRemoteName(s string) bool {
if len(s) < RemoteNameMinLength || len(s) > RemoteNameMaxLength {
return false
}
return validRemoteNameChars.MatchString(s)
}
func (rc *RemoteCluster) PreUpdate() {
if rc.DisplayName == "" {
rc.DisplayName = rc.Name
}
rc.Name = SanitizeUnicode(rc.Name)
rc.DisplayName = SanitizeUnicode(rc.DisplayName)
rc.Name = NormalizeRemoteName(rc.Name)
rc.fixTopics()
}
func (rc *RemoteCluster) IsOnline() bool {
return rc.LastPingAt > GetMillis()-RemoteOfflineAfterMillis
}
// fixTopics ensures all topics are separated by one, and only one, space.
func (rc *RemoteCluster) fixTopics() {
trimmed := strings.TrimSpace(rc.Topics)
if trimmed == "" || trimmed == "*" {
rc.Topics = trimmed
return
}
var sb strings.Builder
sb.WriteString(" ")
ss := strings.Split(rc.Topics, " ")
for _, c := range ss {
cc := strings.TrimSpace(c)
if cc != "" {
sb.WriteString(cc)
sb.WriteString(" ")
}
}
rc.Topics = sb.String()
}
func (rc *RemoteCluster) ToJSON() (string, error) {
b, err := json.Marshal(rc)
if err != nil {
return "", err
}
return string(b), nil
}
func (rc *RemoteCluster) ToRemoteClusterInfo() RemoteClusterInfo {
return RemoteClusterInfo{
Name: rc.Name,
DisplayName: rc.DisplayName,
CreateAt: rc.CreateAt,
LastPingAt: rc.LastPingAt,
}
}
func NormalizeRemoteName(name string) string {
return strings.ToLower(name)
}
func RemoteClusterFromJSON(data io.Reader) (*RemoteCluster, *AppError) {
var rc RemoteCluster
err := json.NewDecoder(data).Decode(&rc)
if err != nil {
return nil, NewAppError("RemoteClusterFromJSON", "model.utils.decode_json.app_error", nil, err.Error(), http.StatusBadRequest)
}
return &rc, nil
}
// RemoteClusterInfo provides a subset of RemoteCluster fields suitable for sending to clients.
type RemoteClusterInfo struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
CreateAt int64 `json:"create_at"`
LastPingAt int64 `json:"last_ping_at"`
}
// RemoteClusterFrame wraps a `RemoteClusterMsg` with credentials specific to a remote cluster.
type RemoteClusterFrame struct {
RemoteId string `json:"remote_id"`
Msg RemoteClusterMsg `json:"msg"`
}
func (f *RemoteClusterFrame) IsValid() *AppError {
if !IsValidId(f.RemoteId) {
return NewAppError("RemoteClusterFrame.IsValid", "api.remote_cluster.invalid_id.app_error", nil, "RemoteId="+f.RemoteId, http.StatusBadRequest)
}
if err := f.Msg.IsValid(); err != nil {
return err
}
return nil
}
func RemoteClusterFrameFromJSON(data io.Reader) (*RemoteClusterFrame, *AppError) {
var frame RemoteClusterFrame
err := json.NewDecoder(data).Decode(&frame)
if err != nil {
return nil, NewAppError("RemoteClusterFrameFromJSON", "model.utils.decode_json.app_error", nil, err.Error(), http.StatusBadRequest)
}
return &frame, nil
}
// RemoteClusterMsg represents a message that is sent and received between clusters.
// These are processed and routed via the RemoteClusters service.
type RemoteClusterMsg struct {
Id string `json:"id"`
Topic string `json:"topic"`
CreateAt int64 `json:"create_at"`
Payload json.RawMessage `json:"payload"`
}
func NewRemoteClusterMsg(topic string, payload json.RawMessage) RemoteClusterMsg {
return RemoteClusterMsg{
Id: NewId(),
Topic: topic,
CreateAt: GetMillis(),
Payload: payload,
}
}
func (m RemoteClusterMsg) IsValid() *AppError {
if !IsValidId(m.Id) {
return NewAppError("RemoteClusterMsg.IsValid", "api.remote_cluster.invalid_id.app_error", nil, "Id="+m.Id, http.StatusBadRequest)
}
if m.Topic == "" {
return NewAppError("RemoteClusterMsg.IsValid", "api.remote_cluster.invalid_topic.app_error", nil, "Topic empty", http.StatusBadRequest)
}
if len(m.Payload) == 0 {
return NewAppError("RemoteClusterMsg.IsValid", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": "PayLoad"}, "", http.StatusBadRequest)
}
return nil
}
func RemoteClusterMsgFromJSON(data io.Reader) (RemoteClusterMsg, *AppError) {
var msg RemoteClusterMsg
err := json.NewDecoder(data).Decode(&msg)
if err != nil {
return RemoteClusterMsg{}, NewAppError("RemoteClusterMsgFromJSON", "model.utils.decode_json.app_error", nil, err.Error(), http.StatusBadRequest)
}
return msg, nil
}
// RemoteClusterPing represents a ping that is sent and received between clusters
// to indicate a connection is alive. This is the payload for a `RemoteClusterMsg`.
type RemoteClusterPing struct {
SentAt int64 `json:"sent_at"`
RecvAt int64 `json:"recv_at"`
}
func RemoteClusterPingFromRawJSON(raw json.RawMessage) (RemoteClusterPing, *AppError) {
var ping RemoteClusterPing
err := json.Unmarshal(raw, &ping)
if err != nil {
return RemoteClusterPing{}, NewAppError("RemoteClusterPingFromRawJSON", "model.utils.decode_json.app_error", nil, err.Error(), http.StatusBadRequest)
}
return ping, nil
}
// RemoteClusterInvite represents an invitation to establish a simple trust with a remote cluster.
type RemoteClusterInvite struct {
RemoteId string `json:"remote_id"`
RemoteTeamId string `json:"remote_team_id"`
SiteURL string `json:"site_url"`
Token string `json:"token"`
}
func RemoteClusterInviteFromRawJSON(raw json.RawMessage) (*RemoteClusterInvite, *AppError) {
var invite RemoteClusterInvite
err := json.Unmarshal(raw, &invite)
if err != nil {
return nil, NewAppError("RemoteClusterInviteFromRawJSON", "model.utils.decode_json.app_error", nil, err.Error(), http.StatusBadRequest)
}
return &invite, nil
}
func (rci *RemoteClusterInvite) Encrypt(password string) ([]byte, error) {
raw, err := json.Marshal(&rci)
if err != nil {
return nil, err
}
// create random salt to be prepended to the blob.
salt := make([]byte, 16)
if _, err = io.ReadFull(rand.Reader, salt); err != nil {
return nil, err
}
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// create random nonce
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// prefix the nonce to the cyphertext so we don't need to keep track of it.
sealed := gcm.Seal(nonce, nonce, raw, nil)
return append(salt, sealed...), nil
}
func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error {
if len(encrypted) <= 16 {
return errors.New("invalid length")
}
// first 16 bytes is the salt that was used to derive a key
salt := encrypted[:16]
encrypted = encrypted[16:]
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return err
}
block, err := aes.NewCipher(key[:])
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// nonce was prefixed to the cyphertext when encrypting so we need to extract it.
nonceSize := gcm.NonceSize()
nonce, cyphertext := encrypted[:nonceSize], encrypted[nonceSize:]
plain, err := gcm.Open(nil, nonce, cyphertext, nil)
if err != nil {
return err
}
// try to unmarshall the decrypted JSON to this invite struct.
return json.Unmarshal(plain, &rci)
}
// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
type RemoteClusterQueryFilter struct {
ExcludeOffline bool
InChannel string
NotInChannel string
Topic string
CreatorId string
OnlyConfirmed bool
}

View File

@ -47,6 +47,12 @@ func init() {
// When updating the values here, the values in mattermost-redux must also be updated.
SysconsoleAncillaryPermissions = map[string][]*Permission{
PERMISSION_SYSCONSOLE_READ_ABOUT_EDITION_AND_LICENSE.Id: {
PERMISSION_READ_LICENSE_INFORMATION,
},
PERMISSION_SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE.Id: {
PERMISSION_MANAGE_LICENSE_INFORMATION,
},
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_CHANNELS.Id: {
PERMISSION_READ_PUBLIC_CHANNEL,
PERMISSION_READ_CHANNEL,
@ -55,19 +61,44 @@ func init() {
},
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_USERS.Id: {
PERMISSION_READ_OTHER_USERS_TEAMS,
PERMISSION_GET_ANALYTICS,
},
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_TEAMS.Id: {
PERMISSION_LIST_PRIVATE_TEAMS,
PERMISSION_LIST_PUBLIC_TEAMS,
PERMISSION_VIEW_TEAM,
},
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT.Id: {
PERMISSION_READ_JOBS,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_ELASTICSEARCH.Id: {
PERMISSION_READ_ELASTICSEARCH_POST_INDEXING_JOB,
PERMISSION_READ_ELASTICSEARCH_POST_AGGREGATION_JOB,
},
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION.Id: {
PERMISSION_READ_JOBS,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_WEB_SERVER.Id: {
PERMISSION_TEST_SITE_URL,
PERMISSION_RELOAD_CONFIG,
PERMISSION_INVALIDATE_CACHES,
},
PERMISSION_SYSCONSOLE_READ_REPORTING.Id: {
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_DATABASE.Id: {
PERMISSION_RECYCLE_DATABASE_CONNECTIONS,
},
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_ELASTICSEARCH.Id: {
PERMISSION_TEST_ELASTICSEARCH,
PERMISSION_CREATE_ELASTICSEARCH_POST_INDEXING_JOB,
PERMISSION_CREATE_ELASTICSEARCH_POST_AGGREGATION_JOB,
PERMISSION_PURGE_ELASTICSEARCH_INDEXES,
},
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_FILE_STORAGE.Id: {
PERMISSION_TEST_S3,
},
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_SMTP.Id: {
PERMISSION_TEST_EMAIL,
},
PERMISSION_SYSCONSOLE_READ_REPORTING_SERVER_LOGS.Id: {
PERMISSION_GET_LOGS,
},
PERMISSION_SYSCONSOLE_READ_REPORTING_SITE_STATISTICS.Id: {
PERMISSION_GET_ANALYTICS,
},
PERMISSION_SYSCONSOLE_READ_REPORTING_TEAM_STATISTICS.Id: {
PERMISSION_VIEW_TEAM,
},
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_USERS.Id: {
@ -102,12 +133,54 @@ func init() {
PERMISSION_CONVERT_PUBLIC_CHANNEL_TO_PRIVATE,
PERMISSION_CONVERT_PRIVATE_CHANNEL_TO_PUBLIC,
},
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT.Id: {
PERMISSION_MANAGE_JOBS,
},
PERMISSION_SYSCONSOLE_WRITE_SITE.Id: {
PERMISSION_SYSCONSOLE_WRITE_SITE_CUSTOMIZATION.Id: {
PERMISSION_EDIT_BRAND,
},
PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_DATA_RETENTION_POLICY.Id: {
PERMISSION_CREATE_DATA_RETENTION_JOB,
},
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY.Id: {
PERMISSION_READ_DATA_RETENTION_JOB,
},
PERMISSION_SYSCONSOLE_WRITE_COMPLIANCE_COMPLIANCE_EXPORT.Id: {
PERMISSION_CREATE_COMPLIANCE_EXPORT_JOB,
PERMISSION_DOWNLOAD_COMPLIANCE_EXPORT_RESULT,
},
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_COMPLIANCE_EXPORT.Id: {
PERMISSION_READ_COMPLIANCE_EXPORT_JOB,
PERMISSION_DOWNLOAD_COMPLIANCE_EXPORT_RESULT,
},
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_CUSTOM_TERMS_OF_SERVICE.Id: {
PERMISSION_READ_AUDITS,
},
PERMISSION_SYSCONSOLE_WRITE_EXPERIMENTAL_BLEVE.Id: {
PERMISSION_CREATE_POST_BLEVE_INDEXES_JOB,
PERMISSION_PURGE_BLEVE_INDEXES,
},
PERMISSION_SYSCONSOLE_WRITE_AUTHENTICATION_LDAP.Id: {
PERMISSION_CREATE_LDAP_SYNC_JOB,
PERMISSION_ADD_LDAP_PUBLIC_CERT,
PERMISSION_REMOVE_LDAP_PUBLIC_CERT,
PERMISSION_ADD_LDAP_PRIVATE_CERT,
PERMISSION_REMOVE_LDAP_PRIVATE_CERT,
},
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_LDAP.Id: {
PERMISSION_TEST_LDAP,
PERMISSION_READ_LDAP_SYNC_JOB,
},
PERMISSION_SYSCONSOLE_WRITE_AUTHENTICATION_EMAIL.Id: {
PERMISSION_INVALIDATE_EMAIL_INVITE,
},
PERMISSION_SYSCONSOLE_WRITE_AUTHENTICATION_SAML.Id: {
PERMISSION_GET_SAML_METADATA_FROM_IDP,
PERMISSION_ADD_SAML_PUBLIC_CERT,
PERMISSION_ADD_SAML_PRIVATE_CERT,
PERMISSION_ADD_SAML_IDP_CERT,
PERMISSION_REMOVE_SAML_PUBLIC_CERT,
PERMISSION_REMOVE_SAML_PRIVATE_CERT,
PERMISSION_REMOVE_SAML_IDP_CERT,
PERMISSION_GET_SAML_CERT_STATUS,
},
}
SystemUserManagerDefaultPermissions = []string{
@ -118,29 +191,76 @@ func init() {
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_GROUPS.Id,
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_TEAMS.Id,
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_CHANNELS.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_SIGNUP.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_EMAIL.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_PASSWORD.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_MFA.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_LDAP.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_SAML.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_OPENID.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_GUEST_ACCESS.Id,
}
SystemReadOnlyAdminDefaultPermissions = []string{
PERMISSION_SYSCONSOLE_READ_ABOUT.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING.Id,
PERMISSION_SYSCONSOLE_READ_ABOUT_EDITION_AND_LICENSE.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING_SITE_STATISTICS.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING_TEAM_STATISTICS.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING_SERVER_LOGS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_USERS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_GROUPS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_TEAMS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_CHANNELS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_PERMISSIONS.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT.Id,
PERMISSION_SYSCONSOLE_READ_SITE.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_WEB_SERVER.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_DATABASE.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_ELASTICSEARCH.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_FILE_STORAGE.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_IMAGE_PROXY.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_SMTP.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_PUSH_NOTIFICATION_SERVER.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_HIGH_AVAILABILITY.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_RATE_LIMITING.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_LOGGING.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_SESSION_LENGTHS.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_PERFORMANCE_MONITORING.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_DEVELOPER.Id,
PERMISSION_SYSCONSOLE_READ_SITE_CUSTOMIZATION.Id,
PERMISSION_SYSCONSOLE_READ_SITE_LOCALIZATION.Id,
PERMISSION_SYSCONSOLE_READ_SITE_USERS_AND_TEAMS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_NOTIFICATIONS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_ANNOUNCEMENT_BANNER.Id,
PERMISSION_SYSCONSOLE_READ_SITE_EMOJI.Id,
PERMISSION_SYSCONSOLE_READ_SITE_POSTS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_FILE_SHARING_AND_DOWNLOADS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_PUBLIC_LINKS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_NOTICES.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_SIGNUP.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_EMAIL.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_PASSWORD.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_MFA.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_LDAP.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_SAML.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_OPENID.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_GUEST_ACCESS.Id,
PERMISSION_SYSCONSOLE_READ_PLUGINS.Id,
PERMISSION_SYSCONSOLE_READ_COMPLIANCE.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS.Id,
PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_INTEGRATION_MANAGEMENT.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_BOT_ACCOUNTS.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_GIF.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_CORS.Id,
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_DATA_RETENTION_POLICY.Id,
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_COMPLIANCE_EXPORT.Id,
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_COMPLIANCE_MONITORING.Id,
PERMISSION_SYSCONSOLE_READ_COMPLIANCE_CUSTOM_TERMS_OF_SERVICE.Id,
PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL_FEATURES.Id,
PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL_FEATURE_FLAGS.Id,
PERMISSION_SYSCONSOLE_READ_EXPERIMENTAL_BLEVE.Id,
}
SystemManagerDefaultPermissions = []string{
PERMISSION_SYSCONSOLE_READ_ABOUT.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING.Id,
PERMISSION_SYSCONSOLE_READ_ABOUT_EDITION_AND_LICENSE.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING_SITE_STATISTICS.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING_TEAM_STATISTICS.Id,
PERMISSION_SYSCONSOLE_READ_REPORTING_SERVER_LOGS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_GROUPS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_TEAMS.Id,
PERMISSION_SYSCONSOLE_READ_USERMANAGEMENT_CHANNELS.Id,
@ -149,20 +269,75 @@ func init() {
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_TEAMS.Id,
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_CHANNELS.Id,
PERMISSION_SYSCONSOLE_WRITE_USERMANAGEMENT_PERMISSIONS.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT.Id,
PERMISSION_SYSCONSOLE_READ_SITE.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_WEB_SERVER.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_DATABASE.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_ELASTICSEARCH.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_FILE_STORAGE.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_IMAGE_PROXY.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_SMTP.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_PUSH_NOTIFICATION_SERVER.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_HIGH_AVAILABILITY.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_RATE_LIMITING.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_LOGGING.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_SESSION_LENGTHS.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_PERFORMANCE_MONITORING.Id,
PERMISSION_SYSCONSOLE_READ_ENVIRONMENT_DEVELOPER.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_WEB_SERVER.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_DATABASE.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_ELASTICSEARCH.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_FILE_STORAGE.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_IMAGE_PROXY.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_SMTP.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_PUSH_NOTIFICATION_SERVER.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_HIGH_AVAILABILITY.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_RATE_LIMITING.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_LOGGING.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_SESSION_LENGTHS.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_PERFORMANCE_MONITORING.Id,
PERMISSION_SYSCONSOLE_WRITE_ENVIRONMENT_DEVELOPER.Id,
PERMISSION_SYSCONSOLE_READ_SITE_CUSTOMIZATION.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_CUSTOMIZATION.Id,
PERMISSION_SYSCONSOLE_READ_SITE_LOCALIZATION.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_LOCALIZATION.Id,
PERMISSION_SYSCONSOLE_READ_SITE_USERS_AND_TEAMS.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_USERS_AND_TEAMS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_NOTIFICATIONS.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_NOTIFICATIONS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_ANNOUNCEMENT_BANNER.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_ANNOUNCEMENT_BANNER.Id,
PERMISSION_SYSCONSOLE_READ_SITE_EMOJI.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_EMOJI.Id,
PERMISSION_SYSCONSOLE_READ_SITE_POSTS.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_POSTS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_FILE_SHARING_AND_DOWNLOADS.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_FILE_SHARING_AND_DOWNLOADS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_PUBLIC_LINKS.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_PUBLIC_LINKS.Id,
PERMISSION_SYSCONSOLE_READ_SITE_NOTICES.Id,
PERMISSION_SYSCONSOLE_WRITE_SITE_NOTICES.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_SIGNUP.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_EMAIL.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_PASSWORD.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_MFA.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_LDAP.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_SAML.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_OPENID.Id,
PERMISSION_SYSCONSOLE_READ_AUTHENTICATION_GUEST_ACCESS.Id,
PERMISSION_SYSCONSOLE_READ_PLUGINS.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS.Id,
PERMISSION_SYSCONSOLE_WRITE_INTEGRATIONS.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_INTEGRATION_MANAGEMENT.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_BOT_ACCOUNTS.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_GIF.Id,
PERMISSION_SYSCONSOLE_READ_INTEGRATIONS_CORS.Id,
PERMISSION_SYSCONSOLE_WRITE_INTEGRATIONS_INTEGRATION_MANAGEMENT.Id,
PERMISSION_SYSCONSOLE_WRITE_INTEGRATIONS_BOT_ACCOUNTS.Id,
PERMISSION_SYSCONSOLE_WRITE_INTEGRATIONS_GIF.Id,
PERMISSION_SYSCONSOLE_WRITE_INTEGRATIONS_CORS.Id,
}
// Add the ancillary permissions to each system role
SystemUserManagerDefaultPermissions = addAncillaryPermissions(SystemUserManagerDefaultPermissions)
SystemReadOnlyAdminDefaultPermissions = addAncillaryPermissions(SystemReadOnlyAdminDefaultPermissions)
SystemManagerDefaultPermissions = addAncillaryPermissions(SystemManagerDefaultPermissions)
SystemUserManagerDefaultPermissions = AddAncillaryPermissions(SystemUserManagerDefaultPermissions)
SystemReadOnlyAdminDefaultPermissions = AddAncillaryPermissions(SystemReadOnlyAdminDefaultPermissions)
SystemManagerDefaultPermissions = AddAncillaryPermissions(SystemManagerDefaultPermissions)
}
type RoleType string
@ -278,7 +453,7 @@ func (r *Role) MergeChannelHigherScopedPermissions(higherScopedPermissions *Role
_, presentOnHigherScope := higherScopedPermissionsMap[cp.Id]
// For the channel admin role always look to the higher scope to determine if the role has ther permission.
// For the channel admin role always look to the higher scope to determine if the role has their permission.
// The channel admin is a special case because they're not part of the UI to be "channel moderated", only
// channel members and channel guests are.
if higherScopedPermissions.RoleID == CHANNEL_ADMIN_ROLE_ID && presentOnHigherScope {
@ -475,7 +650,7 @@ func (r *Role) IsValidWithoutId() bool {
return false
}
if len(r.DisplayName) == 0 || len(r.DisplayName) > ROLE_DISPLAY_NAME_MAX_LENGTH {
if r.DisplayName == "" || len(r.DisplayName) > ROLE_DISPLAY_NAME_MAX_LENGTH {
return false
}
@ -519,7 +694,7 @@ func CleanRoleNames(roleNames []string) ([]string, bool) {
}
func IsValidRoleName(roleName string) bool {
if len(roleName) <= 0 || len(roleName) > ROLE_NAME_MAX_LENGTH {
if roleName == "" || len(roleName) > ROLE_NAME_MAX_LENGTH {
return false
}
@ -765,7 +940,7 @@ func MakeDefaultRoles() map[string]*Role {
return roles
}
func addAncillaryPermissions(permissions []string) []string {
func AddAncillaryPermissions(permissions []string) []string {
for _, permission := range permissions {
if ancillaryPermissions, ok := SysconsoleAncillaryPermissions[permission]; ok {
for _, ancillaryPermission := range ancillaryPermissions {

View File

@ -11,42 +11,65 @@ import (
type TaskFunc func()
type ScheduledTask struct {
Name string `json:"name"`
Interval time.Duration `json:"interval"`
Recurring bool `json:"recurring"`
function func()
cancel chan struct{}
cancelled chan struct{}
Name string `json:"name"`
Interval time.Duration `json:"interval"`
Recurring bool `json:"recurring"`
function func()
cancel chan struct{}
cancelled chan struct{}
fromNextIntervalTime bool
}
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
return createTask(name, function, timeToExecution, false)
return createTask(name, function, timeToExecution, false, false)
}
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
return createTask(name, function, interval, true)
return createTask(name, function, interval, true, false)
}
func createTask(name string, function TaskFunc, interval time.Duration, recurring bool) *ScheduledTask {
func CreateRecurringTaskFromNextIntervalTime(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
return createTask(name, function, interval, true, true)
}
func createTask(name string, function TaskFunc, interval time.Duration, recurring bool, fromNextIntervalTime bool) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: interval,
Recurring: recurring,
function: function,
cancel: make(chan struct{}),
cancelled: make(chan struct{}),
Name: name,
Interval: interval,
Recurring: recurring,
function: function,
cancel: make(chan struct{}),
cancelled: make(chan struct{}),
fromNextIntervalTime: fromNextIntervalTime,
}
go func() {
defer close(task.cancelled)
ticker := time.NewTicker(interval)
var firstTick <-chan time.Time
var ticker *time.Ticker
if task.fromNextIntervalTime {
currTime := time.Now()
first := currTime.Truncate(interval)
if first.Before(currTime) {
first = first.Add(interval)
}
firstTick = time.After(time.Until(first))
ticker = &time.Ticker{C: nil}
} else {
firstTick = nil
ticker = time.NewTicker(interval)
}
defer func() {
ticker.Stop()
}()
for {
select {
case <-firstTick:
ticker = time.NewTicker(interval)
function()
case <-ticker.C:
function()
case <-task.cancel:

View File

@ -101,9 +101,8 @@ func SchemesFromJson(data io.Reader) []*Scheme {
var schemes []*Scheme
if err := json.NewDecoder(data).Decode(&schemes); err == nil {
return schemes
} else {
return nil
}
return nil
}
func (scheme *Scheme) IsValid() bool {
@ -115,7 +114,7 @@ func (scheme *Scheme) IsValid() bool {
}
func (scheme *Scheme) IsValidForCreate() bool {
if len(scheme.DisplayName) == 0 || len(scheme.DisplayName) > SCHEME_DISPLAY_NAME_MAX_LENGTH {
if scheme.DisplayName == "" || len(scheme.DisplayName) > SCHEME_DISPLAY_NAME_MAX_LENGTH {
return false
}
@ -160,15 +159,15 @@ func (scheme *Scheme) IsValidForCreate() bool {
}
if scheme.Scope == SCHEME_SCOPE_CHANNEL {
if len(scheme.DefaultTeamAdminRole) != 0 {
if scheme.DefaultTeamAdminRole != "" {
return false
}
if len(scheme.DefaultTeamUserRole) != 0 {
if scheme.DefaultTeamUserRole != "" {
return false
}
if len(scheme.DefaultTeamGuestRole) != 0 {
if scheme.DefaultTeamGuestRole != "" {
return false
}
}

View File

@ -25,6 +25,8 @@ type SearchParams struct {
ExcludedAfterDate string
BeforeDate string
ExcludedBeforeDate string
Extensions []string
ExcludedExtensions []string
OnDate string
ExcludedDate string
OrTerms bool
@ -106,7 +108,7 @@ func (p *SearchParams) GetExcludedDateMillis() (int64, int64) {
return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
}
var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on"}
var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on", "ext"}
type flag struct {
name string
@ -214,7 +216,7 @@ func parseSearchFlags(input []string) ([]searchWord, []flag) {
// and remove extra pound #s
word = hashtagStart.ReplaceAllString(word, "#")
if len(word) != 0 {
if word != "" {
words = append(words, searchWord{
word,
exclude,
@ -265,6 +267,8 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
excludedBeforeDate := ""
onDate := ""
excludedDate := ""
excludedExtensions := []string{}
extensions := []string{}
for _, flag := range flags {
if flag.name == "in" || flag.name == "channel" {
@ -297,12 +301,18 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
} else {
onDate = flag.value
}
} else if flag.name == "ext" {
if flag.exclude {
excludedExtensions = append(excludedExtensions, flag.value)
} else {
extensions = append(extensions, flag.value)
}
}
}
paramsList := []*SearchParams{}
if len(plainTerms) > 0 || len(excludedPlainTerms) > 0 {
if plainTerms != "" || excludedPlainTerms != "" {
paramsList = append(paramsList, &SearchParams{
Terms: plainTerms,
ExcludedTerms: excludedPlainTerms,
@ -315,13 +325,15 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
ExcludedAfterDate: excludedAfterDate,
BeforeDate: beforeDate,
ExcludedBeforeDate: excludedBeforeDate,
Extensions: extensions,
ExcludedExtensions: excludedExtensions,
OnDate: onDate,
ExcludedDate: excludedDate,
TimeZoneOffset: timeZoneOffset,
})
}
if len(hashtagTerms) > 0 || len(excludedHashtagTerms) > 0 {
if hashtagTerms != "" || excludedHashtagTerms != "" {
paramsList = append(paramsList, &SearchParams{
Terms: hashtagTerms,
ExcludedTerms: excludedHashtagTerms,
@ -334,6 +346,8 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
ExcludedAfterDate: excludedAfterDate,
BeforeDate: beforeDate,
ExcludedBeforeDate: excludedBeforeDate,
Extensions: extensions,
ExcludedExtensions: excludedExtensions,
OnDate: onDate,
ExcludedDate: excludedDate,
TimeZoneOffset: timeZoneOffset,
@ -341,13 +355,14 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
}
// special case for when no terms are specified but we still have a filter
if len(plainTerms) == 0 && len(hashtagTerms) == 0 &&
len(excludedPlainTerms) == 0 && len(excludedHashtagTerms) == 0 &&
if plainTerms == "" && hashtagTerms == "" &&
excludedPlainTerms == "" && excludedHashtagTerms == "" &&
(len(inChannels) != 0 || len(fromUsers) != 0 ||
len(excludedChannels) != 0 || len(excludedUsers) != 0 ||
len(afterDate) != 0 || len(excludedAfterDate) != 0 ||
len(beforeDate) != 0 || len(excludedBeforeDate) != 0 ||
len(onDate) != 0 || len(excludedDate) != 0) {
len(extensions) != 0 || len(excludedExtensions) != 0 ||
afterDate != "" || excludedAfterDate != "" ||
beforeDate != "" || excludedBeforeDate != "" ||
onDate != "" || excludedDate != "") {
paramsList = append(paramsList, &SearchParams{
Terms: "",
ExcludedTerms: "",
@ -360,6 +375,8 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
ExcludedAfterDate: excludedAfterDate,
BeforeDate: beforeDate,
ExcludedBeforeDate: excludedBeforeDate,
Extensions: extensions,
ExcludedExtensions: excludedExtensions,
OnDate: onDate,
ExcludedDate: excludedDate,
TimeZoneOffset: timeZoneOffset,

View File

@ -15,8 +15,8 @@ type SecurityBulletin struct {
type SecurityBulletins []SecurityBulletin
func (me *SecurityBulletin) ToJson() string {
b, _ := json.Marshal(me)
func (sb *SecurityBulletin) ToJson() string {
b, _ := json.Marshal(sb)
return string(b)
}
@ -26,12 +26,12 @@ func SecurityBulletinFromJson(data io.Reader) *SecurityBulletin {
return o
}
func (me SecurityBulletins) ToJson() string {
if b, err := json.Marshal(me); err != nil {
func (sb SecurityBulletins) ToJson() string {
b, err := json.Marshal(sb)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func SecurityBulletinsFromJson(data io.Reader) SecurityBulletins {

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ import (
"strconv"
"strings"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
const (
@ -25,11 +25,16 @@ const (
SESSION_PROP_IS_BOT = "is_bot"
SESSION_PROP_IS_BOT_VALUE = "true"
SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken"
SESSION_TYPE_CLOUD_KEY = "CloudKey"
SESSION_TYPE_REMOTECLUSTER_TOKEN = "RemoteClusterToken"
SESSION_PROP_IS_GUEST = "is_guest"
SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years
)
//msgp StringMap
type StringMap map[string]string
//msgp:tuple Session
// Session contains the user session details.
@ -53,20 +58,20 @@ type Session struct {
// Returns true if the session is unrestricted, which should grant it
// with all permissions. This is used for local mode sessions
func (me *Session) IsUnrestricted() bool {
return me.Local
func (s *Session) IsUnrestricted() bool {
return s.Local
}
func (me *Session) DeepCopy() *Session {
copySession := *me
func (s *Session) DeepCopy() *Session {
copySession := *s
if me.Props != nil {
copySession.Props = CopyStringMap(me.Props)
if s.Props != nil {
copySession.Props = CopyStringMap(s.Props)
}
if me.TeamMembers != nil {
copySession.TeamMembers = make([]*TeamMember, len(me.TeamMembers))
for index, tm := range me.TeamMembers {
if s.TeamMembers != nil {
copySession.TeamMembers = make([]*TeamMember, len(s.TeamMembers))
for index, tm := range s.TeamMembers {
copySession.TeamMembers[index] = new(TeamMember)
*copySession.TeamMembers[index] = *tm
}
@ -75,45 +80,45 @@ func (me *Session) DeepCopy() *Session {
return &copySession
}
func (me *Session) ToJson() string {
b, _ := json.Marshal(me)
func (s *Session) ToJson() string {
b, _ := json.Marshal(s)
return string(b)
}
func SessionFromJson(data io.Reader) *Session {
var me *Session
json.NewDecoder(data).Decode(&me)
return me
var s *Session
json.NewDecoder(data).Decode(&s)
return s
}
func (me *Session) PreSave() {
if me.Id == "" {
me.Id = NewId()
func (s *Session) PreSave() {
if s.Id == "" {
s.Id = NewId()
}
if me.Token == "" {
me.Token = NewId()
if s.Token == "" {
s.Token = NewId()
}
me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt
s.CreateAt = GetMillis()
s.LastActivityAt = s.CreateAt
if me.Props == nil {
me.Props = make(map[string]string)
if s.Props == nil {
s.Props = make(map[string]string)
}
}
func (me *Session) Sanitize() {
me.Token = ""
func (s *Session) Sanitize() {
s.Token = ""
}
func (me *Session) IsExpired() bool {
func (s *Session) IsExpired() bool {
if me.ExpiresAt <= 0 {
if s.ExpiresAt <= 0 {
return false
}
if GetMillis() > me.ExpiresAt {
if GetMillis() > s.ExpiresAt {
return true
}
@ -123,25 +128,25 @@ func (me *Session) IsExpired() bool {
// Deprecated: SetExpireInDays is deprecated and should not be used.
// Use (*App).SetSessionExpireInDays instead which handles the
// cases where the new ExpiresAt is not relative to CreateAt.
func (me *Session) SetExpireInDays(days int) {
if me.CreateAt == 0 {
me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
func (s *Session) SetExpireInDays(days int) {
if s.CreateAt == 0 {
s.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
} else {
me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
s.ExpiresAt = s.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
}
}
func (me *Session) AddProp(key string, value string) {
func (s *Session) AddProp(key string, value string) {
if me.Props == nil {
me.Props = make(map[string]string)
if s.Props == nil {
s.Props = make(map[string]string)
}
me.Props[key] = value
s.Props[key] = value
}
func (me *Session) GetTeamByTeamId(teamId string) *TeamMember {
for _, team := range me.TeamMembers {
func (s *Session) GetTeamByTeamId(teamId string) *TeamMember {
for _, team := range s.TeamMembers {
if team.TeamId == teamId {
return team
}
@ -150,77 +155,77 @@ func (me *Session) GetTeamByTeamId(teamId string) *TeamMember {
return nil
}
func (me *Session) IsMobileApp() bool {
return len(me.DeviceId) > 0 || me.IsMobile()
func (s *Session) IsMobileApp() bool {
return s.DeviceId != "" || s.IsMobile()
}
func (me *Session) IsMobile() bool {
val, ok := me.Props[USER_AUTH_SERVICE_IS_MOBILE]
func (s *Session) IsMobile() bool {
val, ok := s.Props[USER_AUTH_SERVICE_IS_MOBILE]
if !ok {
return false
}
isMobile, err := strconv.ParseBool(val)
if err != nil {
mlog.Error("Error parsing boolean property from Session", mlog.Err(err))
mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
return false
}
return isMobile
}
func (me *Session) IsSaml() bool {
val, ok := me.Props[USER_AUTH_SERVICE_IS_SAML]
func (s *Session) IsSaml() bool {
val, ok := s.Props[USER_AUTH_SERVICE_IS_SAML]
if !ok {
return false
}
isSaml, err := strconv.ParseBool(val)
if err != nil {
mlog.Error("Error parsing boolean property from Session", mlog.Err(err))
mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
return false
}
return isSaml
}
func (me *Session) IsOAuthUser() bool {
val, ok := me.Props[USER_AUTH_SERVICE_IS_OAUTH]
func (s *Session) IsOAuthUser() bool {
val, ok := s.Props[USER_AUTH_SERVICE_IS_OAUTH]
if !ok {
return false
}
isOAuthUser, err := strconv.ParseBool(val)
if err != nil {
mlog.Error("Error parsing boolean property from Session", mlog.Err(err))
mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
return false
}
return isOAuthUser
}
func (me *Session) IsSSOLogin() bool {
return me.IsOAuthUser() || me.IsSaml()
func (s *Session) IsSSOLogin() bool {
return s.IsOAuthUser() || s.IsSaml()
}
func (me *Session) GetUserRoles() []string {
return strings.Fields(me.Roles)
func (s *Session) GetUserRoles() []string {
return strings.Fields(s.Roles)
}
func (me *Session) GenerateCSRF() string {
func (s *Session) GenerateCSRF() string {
token := NewId()
me.AddProp("csrf", token)
s.AddProp("csrf", token)
return token
}
func (me *Session) GetCSRF() string {
if me.Props == nil {
func (s *Session) GetCSRF() string {
if s.Props == nil {
return ""
}
return me.Props["csrf"]
return s.Props["csrf"]
}
func SessionsToJson(o []*Session) string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func SessionsFromJson(data io.Reader) []*Session {

View File

@ -0,0 +1,540 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *Session) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 13 {
err = msgp.ArrayError{Wanted: 13, Got: zb0001}
return
}
z.Id, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
z.Token, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Token")
return
}
z.CreateAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "CreateAt")
return
}
z.ExpiresAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "ExpiresAt")
return
}
z.LastActivityAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "LastActivityAt")
return
}
z.UserId, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "UserId")
return
}
z.DeviceId, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "DeviceId")
return
}
z.Roles, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
z.IsOAuth, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "IsOAuth")
return
}
z.ExpiredNotify, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "ExpiredNotify")
return
}
var zb0002 uint32
zb0002, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
if z.Props == nil {
z.Props = make(StringMap, zb0002)
} else if len(z.Props) > 0 {
for key := range z.Props {
delete(z.Props, key)
}
}
for zb0002 > 0 {
zb0002--
var za0001 string
var za0002 string
za0001, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
za0002, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Props", za0001)
return
}
z.Props[za0001] = za0002
}
var zb0003 uint32
zb0003, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err, "TeamMembers")
return
}
if cap(z.TeamMembers) >= int(zb0003) {
z.TeamMembers = (z.TeamMembers)[:zb0003]
} else {
z.TeamMembers = make([]*TeamMember, zb0003)
}
for za0003 := range z.TeamMembers {
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
err = msgp.WrapError(err, "TeamMembers", za0003)
return
}
z.TeamMembers[za0003] = nil
} else {
if z.TeamMembers[za0003] == nil {
z.TeamMembers[za0003] = new(TeamMember)
}
err = z.TeamMembers[za0003].DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "TeamMembers", za0003)
return
}
}
}
z.Local, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "Local")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *Session) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 13
err = en.Append(0x9d)
if err != nil {
return
}
err = en.WriteString(z.Id)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
err = en.WriteString(z.Token)
if err != nil {
err = msgp.WrapError(err, "Token")
return
}
err = en.WriteInt64(z.CreateAt)
if err != nil {
err = msgp.WrapError(err, "CreateAt")
return
}
err = en.WriteInt64(z.ExpiresAt)
if err != nil {
err = msgp.WrapError(err, "ExpiresAt")
return
}
err = en.WriteInt64(z.LastActivityAt)
if err != nil {
err = msgp.WrapError(err, "LastActivityAt")
return
}
err = en.WriteString(z.UserId)
if err != nil {
err = msgp.WrapError(err, "UserId")
return
}
err = en.WriteString(z.DeviceId)
if err != nil {
err = msgp.WrapError(err, "DeviceId")
return
}
err = en.WriteString(z.Roles)
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
err = en.WriteBool(z.IsOAuth)
if err != nil {
err = msgp.WrapError(err, "IsOAuth")
return
}
err = en.WriteBool(z.ExpiredNotify)
if err != nil {
err = msgp.WrapError(err, "ExpiredNotify")
return
}
err = en.WriteMapHeader(uint32(len(z.Props)))
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
for za0001, za0002 := range z.Props {
err = en.WriteString(za0001)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
err = en.WriteString(za0002)
if err != nil {
err = msgp.WrapError(err, "Props", za0001)
return
}
}
err = en.WriteArrayHeader(uint32(len(z.TeamMembers)))
if err != nil {
err = msgp.WrapError(err, "TeamMembers")
return
}
for za0003 := range z.TeamMembers {
if z.TeamMembers[za0003] == nil {
err = en.WriteNil()
if err != nil {
return
}
} else {
err = z.TeamMembers[za0003].EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "TeamMembers", za0003)
return
}
}
}
err = en.WriteBool(z.Local)
if err != nil {
err = msgp.WrapError(err, "Local")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *Session) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 13
o = append(o, 0x9d)
o = msgp.AppendString(o, z.Id)
o = msgp.AppendString(o, z.Token)
o = msgp.AppendInt64(o, z.CreateAt)
o = msgp.AppendInt64(o, z.ExpiresAt)
o = msgp.AppendInt64(o, z.LastActivityAt)
o = msgp.AppendString(o, z.UserId)
o = msgp.AppendString(o, z.DeviceId)
o = msgp.AppendString(o, z.Roles)
o = msgp.AppendBool(o, z.IsOAuth)
o = msgp.AppendBool(o, z.ExpiredNotify)
o = msgp.AppendMapHeader(o, uint32(len(z.Props)))
for za0001, za0002 := range z.Props {
o = msgp.AppendString(o, za0001)
o = msgp.AppendString(o, za0002)
}
o = msgp.AppendArrayHeader(o, uint32(len(z.TeamMembers)))
for za0003 := range z.TeamMembers {
if z.TeamMembers[za0003] == nil {
o = msgp.AppendNil(o)
} else {
o, err = z.TeamMembers[za0003].MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "TeamMembers", za0003)
return
}
}
}
o = msgp.AppendBool(o, z.Local)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *Session) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 13 {
err = msgp.ArrayError{Wanted: 13, Got: zb0001}
return
}
z.Id, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
z.Token, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Token")
return
}
z.CreateAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "CreateAt")
return
}
z.ExpiresAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "ExpiresAt")
return
}
z.LastActivityAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "LastActivityAt")
return
}
z.UserId, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "UserId")
return
}
z.DeviceId, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "DeviceId")
return
}
z.Roles, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
z.IsOAuth, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "IsOAuth")
return
}
z.ExpiredNotify, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "ExpiredNotify")
return
}
var zb0002 uint32
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
if z.Props == nil {
z.Props = make(StringMap, zb0002)
} else if len(z.Props) > 0 {
for key := range z.Props {
delete(z.Props, key)
}
}
for zb0002 > 0 {
var za0001 string
var za0002 string
zb0002--
za0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
za0002, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Props", za0001)
return
}
z.Props[za0001] = za0002
}
var zb0003 uint32
zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "TeamMembers")
return
}
if cap(z.TeamMembers) >= int(zb0003) {
z.TeamMembers = (z.TeamMembers)[:zb0003]
} else {
z.TeamMembers = make([]*TeamMember, zb0003)
}
for za0003 := range z.TeamMembers {
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
if err != nil {
return
}
z.TeamMembers[za0003] = nil
} else {
if z.TeamMembers[za0003] == nil {
z.TeamMembers[za0003] = new(TeamMember)
}
bts, err = z.TeamMembers[za0003].UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "TeamMembers", za0003)
return
}
}
}
z.Local, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Local")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *Session) Msgsize() (s int) {
s = 1 + msgp.StringPrefixSize + len(z.Id) + msgp.StringPrefixSize + len(z.Token) + msgp.Int64Size + msgp.Int64Size + msgp.Int64Size + msgp.StringPrefixSize + len(z.UserId) + msgp.StringPrefixSize + len(z.DeviceId) + msgp.StringPrefixSize + len(z.Roles) + msgp.BoolSize + msgp.BoolSize + msgp.MapHeaderSize
if z.Props != nil {
for za0001, za0002 := range z.Props {
_ = za0002
s += msgp.StringPrefixSize + len(za0001) + msgp.StringPrefixSize + len(za0002)
}
}
s += msgp.ArrayHeaderSize
for za0003 := range z.TeamMembers {
if z.TeamMembers[za0003] == nil {
s += msgp.NilSize
} else {
s += z.TeamMembers[za0003].Msgsize()
}
}
s += msgp.BoolSize
return
}
// DecodeMsg implements msgp.Decodable
func (z *StringMap) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0003 uint32
zb0003, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if (*z) == nil {
(*z) = make(StringMap, zb0003)
} else if len((*z)) > 0 {
for key := range *z {
delete((*z), key)
}
}
for zb0003 > 0 {
zb0003--
var zb0001 string
var zb0002 string
zb0001, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err)
return
}
zb0002, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
(*z)[zb0001] = zb0002
}
return
}
// EncodeMsg implements msgp.Encodable
func (z StringMap) EncodeMsg(en *msgp.Writer) (err error) {
err = en.WriteMapHeader(uint32(len(z)))
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0004, zb0005 := range z {
err = en.WriteString(zb0004)
if err != nil {
err = msgp.WrapError(err)
return
}
err = en.WriteString(zb0005)
if err != nil {
err = msgp.WrapError(err, zb0004)
return
}
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z StringMap) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendMapHeader(o, uint32(len(z)))
for zb0004, zb0005 := range z {
o = msgp.AppendString(o, zb0004)
o = msgp.AppendString(o, zb0005)
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *StringMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0003 uint32
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if (*z) == nil {
(*z) = make(StringMap, zb0003)
} else if len((*z)) > 0 {
for key := range *z {
delete((*z), key)
}
}
for zb0003 > 0 {
var zb0001 string
var zb0002 string
zb0003--
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
zb0002, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
(*z)[zb0001] = zb0002
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z StringMap) Msgsize() (s int) {
s = msgp.MapHeaderSize
if z != nil {
for zb0004, zb0005 := range z {
_ = zb0005
s += msgp.StringPrefixSize + len(zb0004) + msgp.StringPrefixSize + len(zb0005)
}
}
return
}

View File

@ -0,0 +1,273 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"unicode/utf8"
)
// SharedChannel represents a channel that can be synchronized with a remote cluster.
// If "home" is true, then the shared channel is homed locally and "SharedChannelRemote"
// table contains the remote clusters that have been invited.
// If "home" is false, then the shared channel is homed remotely, and "RemoteId"
// field points to the remote cluster connection in "RemoteClusters" table.
type SharedChannel struct {
ChannelId string `json:"id"`
TeamId string `json:"team_id"`
Home bool `json:"home"`
ReadOnly bool `json:"readonly"`
ShareName string `json:"name"`
ShareDisplayName string `json:"display_name"`
SharePurpose string `json:"purpose"`
ShareHeader string `json:"header"`
CreatorId string `json:"creator_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
RemoteId string `json:"remote_id,omitempty"` // if not "home"
Type string `db:"-"`
}
func (sc *SharedChannel) ToJson() string {
b, _ := json.Marshal(sc)
return string(b)
}
func SharedChannelFromJson(data io.Reader) (*SharedChannel, error) {
var sc *SharedChannel
err := json.NewDecoder(data).Decode(&sc)
return sc, err
}
func (sc *SharedChannel) IsValid() *AppError {
if !IsValidId(sc.ChannelId) {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.id.app_error", nil, "ChannelId="+sc.ChannelId, http.StatusBadRequest)
}
if sc.Type != CHANNEL_DIRECT && !IsValidId(sc.TeamId) {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.id.app_error", nil, "TeamId="+sc.TeamId, http.StatusBadRequest)
}
if sc.CreateAt == 0 {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if sc.UpdateAt == 0 {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if utf8.RuneCountInString(sc.ShareDisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if !IsValidChannelIdentifier(sc.ShareName) {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if utf8.RuneCountInString(sc.ShareHeader) > CHANNEL_HEADER_MAX_RUNES {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if utf8.RuneCountInString(sc.SharePurpose) > CHANNEL_PURPOSE_MAX_RUNES {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if !IsValidId(sc.CreatorId) {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "CreatorId="+sc.CreatorId, http.StatusBadRequest)
}
if !sc.Home {
if !IsValidId(sc.RemoteId) {
return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.id.app_error", nil, "RemoteId="+sc.RemoteId, http.StatusBadRequest)
}
}
return nil
}
func (sc *SharedChannel) PreSave() {
sc.ShareName = SanitizeUnicode(sc.ShareName)
sc.ShareDisplayName = SanitizeUnicode(sc.ShareDisplayName)
sc.CreateAt = GetMillis()
sc.UpdateAt = sc.CreateAt
}
func (sc *SharedChannel) PreUpdate() {
sc.UpdateAt = GetMillis()
sc.ShareName = SanitizeUnicode(sc.ShareName)
sc.ShareDisplayName = SanitizeUnicode(sc.ShareDisplayName)
}
// SharedChannelRemote represents a remote cluster that has been invited
// to a shared channel.
type SharedChannelRemote struct {
Id string `json:"id"`
ChannelId string `json:"channel_id"`
CreatorId string `json:"creator_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
IsInviteAccepted bool `json:"is_invite_accepted"`
IsInviteConfirmed bool `json:"is_invite_confirmed"`
RemoteId string `json:"remote_id"`
LastPostUpdateAt int64 `json:"last_post_update_at"`
LastPostId string `json:"last_post_id"`
}
func (sc *SharedChannelRemote) ToJson() string {
b, _ := json.Marshal(sc)
return string(b)
}
func SharedChannelRemoteFromJson(data io.Reader) (*SharedChannelRemote, error) {
var sc *SharedChannelRemote
err := json.NewDecoder(data).Decode(&sc)
return sc, err
}
func (sc *SharedChannelRemote) IsValid() *AppError {
if !IsValidId(sc.Id) {
return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.id.app_error", nil, "Id="+sc.Id, http.StatusBadRequest)
}
if !IsValidId(sc.ChannelId) {
return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.id.app_error", nil, "ChannelId="+sc.ChannelId, http.StatusBadRequest)
}
if sc.CreateAt == 0 {
return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if sc.UpdateAt == 0 {
return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
}
if !IsValidId(sc.CreatorId) {
return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "id="+sc.CreatorId, http.StatusBadRequest)
}
return nil
}
func (sc *SharedChannelRemote) PreSave() {
if sc.Id == "" {
sc.Id = NewId()
}
sc.CreateAt = GetMillis()
sc.UpdateAt = sc.CreateAt
}
func (sc *SharedChannelRemote) PreUpdate() {
sc.UpdateAt = GetMillis()
}
type SharedChannelRemoteStatus struct {
ChannelId string `json:"channel_id"`
DisplayName string `json:"display_name"`
SiteURL string `json:"site_url"`
LastPingAt int64 `json:"last_ping_at"`
NextSyncAt int64 `json:"next_sync_at"`
ReadOnly bool `json:"readonly"`
IsInviteAccepted bool `json:"is_invite_accepted"`
Token string `json:"token"`
}
// SharedChannelUser stores a lastSyncAt timestamp on behalf of a remote cluster for
// each user that has been synchronized.
type SharedChannelUser struct {
Id string `json:"id"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
RemoteId string `json:"remote_id"`
CreateAt int64 `json:"create_at"`
LastSyncAt int64 `json:"last_sync_at"`
}
func (scu *SharedChannelUser) PreSave() {
scu.Id = NewId()
scu.CreateAt = GetMillis()
}
func (scu *SharedChannelUser) IsValid() *AppError {
if !IsValidId(scu.Id) {
return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "Id="+scu.Id, http.StatusBadRequest)
}
if !IsValidId(scu.UserId) {
return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "UserId="+scu.UserId, http.StatusBadRequest)
}
if !IsValidId(scu.ChannelId) {
return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "ChannelId="+scu.ChannelId, http.StatusBadRequest)
}
if !IsValidId(scu.RemoteId) {
return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "RemoteId="+scu.RemoteId, http.StatusBadRequest)
}
if scu.CreateAt == 0 {
return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
type GetUsersForSyncFilter struct {
CheckProfileImage bool
ChannelID string
Limit uint64
}
// SharedChannelAttachment stores a lastSyncAt timestamp on behalf of a remote cluster for
// each file attachment that has been synchronized.
type SharedChannelAttachment struct {
Id string `json:"id"`
FileId string `json:"file_id"`
RemoteId string `json:"remote_id"`
CreateAt int64 `json:"create_at"`
LastSyncAt int64 `json:"last_sync_at"`
}
func (scf *SharedChannelAttachment) PreSave() {
if scf.Id == "" {
scf.Id = NewId()
}
if scf.CreateAt == 0 {
scf.CreateAt = GetMillis()
scf.LastSyncAt = scf.CreateAt
} else {
scf.LastSyncAt = GetMillis()
}
}
func (scf *SharedChannelAttachment) IsValid() *AppError {
if !IsValidId(scf.Id) {
return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.id.app_error", nil, "Id="+scf.Id, http.StatusBadRequest)
}
if !IsValidId(scf.FileId) {
return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.id.app_error", nil, "FileId="+scf.FileId, http.StatusBadRequest)
}
if !IsValidId(scf.RemoteId) {
return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.id.app_error", nil, "RemoteId="+scf.RemoteId, http.StatusBadRequest)
}
if scf.CreateAt == 0 {
return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
type SharedChannelFilterOpts struct {
TeamId string
CreatorId string
ExcludeHome bool
ExcludeRemote bool
}
type SharedChannelRemoteFilterOpts struct {
ChannelId string
RemoteId string
InclUnconfirmed bool
}

View File

@ -179,6 +179,9 @@ func ParseSlackAttachment(post *Post, attachments []*SlackAttachment) {
attachment.Pretext = ParseSlackLinksToMarkdown(attachment.Pretext)
for _, field := range attachment.Fields {
if field == nil {
continue
}
if value, ok := field.Value.(string); ok {
field.Value = ParseSlackLinksToMarkdown(value)
}

View File

@ -25,6 +25,8 @@ type Status struct {
Manual bool `json:"manual"`
LastActivityAt int64 `json:"last_activity_at"`
ActiveChannel string `json:"active_channel,omitempty" db:"-"`
DNDEndTime int64 `json:"dnd_end_time"`
PrevStatus string `json:"-"`
}
func (o *Status) ToJson() string {

View File

@ -34,14 +34,16 @@ func (o *SwitchRequest) EmailToOAuth() bool {
(o.NewService == USER_AUTH_SERVICE_SAML ||
o.NewService == USER_AUTH_SERVICE_GITLAB ||
o.NewService == SERVICE_GOOGLE ||
o.NewService == SERVICE_OFFICE365)
o.NewService == SERVICE_OFFICE365 ||
o.NewService == SERVICE_OPENID)
}
func (o *SwitchRequest) OAuthToEmail() bool {
return (o.CurrentService == USER_AUTH_SERVICE_SAML ||
o.CurrentService == USER_AUTH_SERVICE_GITLAB ||
o.CurrentService == SERVICE_GOOGLE ||
o.CurrentService == SERVICE_OFFICE365) && o.NewService == USER_AUTH_SERVICE_EMAIL
o.CurrentService == SERVICE_OFFICE365 ||
o.CurrentService == SERVICE_OPENID) && o.NewService == USER_AUTH_SERVICE_EMAIL
}
func (o *SwitchRequest) EmailToLdap() bool {

View File

@ -14,6 +14,7 @@ const (
SYSTEM_RAN_UNIT_TESTS = "RanUnitTests"
SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime"
SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId"
SYSTEM_LICENSE_RENEWAL_TOKEN = "LicenseRenewalToken"
SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime"
SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey"
SYSTEM_POST_ACTION_COOKIE_SECRET = "PostActionCookieSecret"
@ -31,10 +32,13 @@ const (
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500 = "warn_metric_number_of_active_users_500"
SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M = "warn_metric_number_of_posts_2M"
SYSTEM_WARN_METRIC_LAST_RUN_TIMESTAMP_KEY = "LastWarnMetricRunTimestamp"
SYSTEM_METRIC_SUPPORT_EMAIL_NOT_CONFIGURED = "warn_metric_support_email_not_configured"
SYSTEM_FIRST_ADMIN_VISIT_MARKETPLACE = "FirstAdminVisitMarketplace"
AWS_METERING_REPORT_INTERVAL = 1
AWS_METERING_DIMENSION_USAGE_HRS = "UsageHrs"
USER_LIMIT_OVERAGE_CYCLE_END_DATE = "UserLimitOverageCycleEndDate"
OVER_USER_LIMIT_FORGIVEN_COUNT = "OverUserLimitForgivenCount"
OVER_USER_LIMIT_LAST_EMAIL_SENT = "OverUserLimitLastEmailSent"
)
const (
@ -85,6 +89,22 @@ type ServerBusyState struct {
Expires_ts string `json:"expires_ts,omitempty"`
}
type SupportPacket struct {
ServerOS string `yaml:"server_os"`
ServerArchitecture string `yaml:"server_architecture"`
DatabaseType string `yaml:"database_type"`
DatabaseVersion string `yaml:"database_version"`
LdapVendorName string `yaml:"ldap_vendor_name,omitempty"`
LdapVendorVersion string `yaml:"ldap_vendor_version,omitempty"`
ElasticServerVersion string `yaml:"elastic_server_version,omitempty"`
ElasticServerPlugins []string `yaml:"elastic_server_plugins,omitempty"`
}
type FileData struct {
Filename string
Body []byte
}
func (sbs *ServerBusyState) ToJson() string {
b, _ := json.Marshal(sbs)
return string(b)
@ -151,13 +171,21 @@ var WarnMetricsTable = map[string]WarnMetric{
IsBotOnly: false,
IsRunOnce: true,
},
SYSTEM_METRIC_SUPPORT_EMAIL_NOT_CONFIGURED: {
Id: SYSTEM_METRIC_SUPPORT_EMAIL_NOT_CONFIGURED,
Limit: -1,
IsBotOnly: true,
IsRunOnce: false,
SkipAction: true,
},
}
type WarnMetric struct {
Id string
Limit int64
IsBotOnly bool
IsRunOnce bool
Id string
Limit int64
IsBotOnly bool
IsRunOnce bool
SkipAction bool
}
type WarnMetricDisplayTexts struct {
@ -182,9 +210,8 @@ func WarnMetricStatusFromJson(data io.Reader) *WarnMetricStatus {
var o WarnMetricStatus
if err := json.NewDecoder(data).Decode(&o); err != nil {
return nil
} else {
return &o
}
return &o
}
func MapWarnMetricStatusToJson(o map[string]*WarnMetricStatus) string {

View File

@ -42,6 +42,7 @@ type Team struct {
LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"`
SchemeId *string `json:"scheme_id"`
GroupConstrained *bool `json:"group_constrained"`
PolicyID *string `json:"policy_id" db:"-"`
}
type TeamPatch struct {
@ -152,7 +153,7 @@ func (o *Team) IsValid() *AppError {
return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.Email) > 0 && !IsValidEmail(o.Email) {
if o.Email != "" && !IsValidEmail(o.Email) {
return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
@ -168,7 +169,7 @@ func (o *Team) IsValid() *AppError {
return NewAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.InviteId) == 0 {
if o.InviteId == "" {
return NewAppError("Team.IsValid", "model.team.is_valid.invite_id.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
@ -208,7 +209,7 @@ func (o *Team) PreSave() {
o.Description = SanitizeUnicode(o.Description)
o.CompanyName = SanitizeUnicode(o.CompanyName)
if len(o.InviteId) == 0 {
if o.InviteId == "" {
o.InviteId = NewId()
}
}

View File

@ -15,6 +15,9 @@ const (
USERNAME = "Username"
)
//msgp:tuple TeamMember
// This struct's serializer methods are auto-generated. If a new field is added/removed,
// please run make gen-serialized.
type TeamMember struct {
TeamId string `json:"team_id"`
UserId string `json:"user_id"`
@ -26,28 +29,37 @@ type TeamMember struct {
ExplicitRoles string `json:"explicit_roles"`
}
//msgp:ignore TeamUnread
type TeamUnread struct {
TeamId string `json:"team_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
TeamId string `json:"team_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
MsgCountRoot int64 `json:"msg_count_root"`
ThreadCount int64 `json:"thread_count"`
ThreadMentionCount int64 `json:"thread_mention_count"`
}
//msgp:ignore TeamMemberForExport
type TeamMemberForExport struct {
TeamMember
TeamName string
}
//msgp:ignore TeamMemberWithError
type TeamMemberWithError struct {
UserId string `json:"user_id"`
Member *TeamMember `json:"member"`
Error *AppError `json:"error"`
}
//msgp:ignore EmailInviteWithError
type EmailInviteWithError struct {
Email string `json:"email"`
Error *AppError `json:"error"`
}
//msgp:ignore TeamMembersGetOptions
type TeamMembersGetOptions struct {
// Sort the team members. Accepts "Username", but defaults to "Id".
Sort string
@ -98,11 +110,11 @@ func EmailInviteWithErrorToEmails(o []*EmailInviteWithError) []string {
}
func EmailInviteWithErrorToJson(o []*EmailInviteWithError) string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func EmailInviteWithErrorToString(o *EmailInviteWithError) string {
@ -120,11 +132,11 @@ func TeamMembersWithErrorToTeamMembers(o []*TeamMemberWithError) []*TeamMember {
}
func TeamMembersWithErrorToJson(o []*TeamMemberWithError) string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func TeamMemberWithErrorToString(o *TeamMemberWithError) string {
@ -138,11 +150,11 @@ func TeamMembersWithErrorFromJson(data io.Reader) []*TeamMemberWithError {
}
func TeamMembersToJson(o []*TeamMember) string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func TeamMembersFromJson(data io.Reader) []*TeamMember {
@ -152,11 +164,11 @@ func TeamMembersFromJson(data io.Reader) []*TeamMember {
}
func TeamsUnreadToJson(o []*TeamUnread) string {
if b, err := json.Marshal(o); err != nil {
b, err := json.Marshal(o)
if err != nil {
return "[]"
} else {
return string(b)
}
return string(b)
}
func TeamsUnreadFromJson(data io.Reader) []*TeamUnread {

View File

@ -0,0 +1,193 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *TeamMember) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 8 {
err = msgp.ArrayError{Wanted: 8, Got: zb0001}
return
}
z.TeamId, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "TeamId")
return
}
z.UserId, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "UserId")
return
}
z.Roles, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
z.DeleteAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "DeleteAt")
return
}
z.SchemeGuest, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "SchemeGuest")
return
}
z.SchemeUser, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "SchemeUser")
return
}
z.SchemeAdmin, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "SchemeAdmin")
return
}
z.ExplicitRoles, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "ExplicitRoles")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *TeamMember) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 8
err = en.Append(0x98)
if err != nil {
return
}
err = en.WriteString(z.TeamId)
if err != nil {
err = msgp.WrapError(err, "TeamId")
return
}
err = en.WriteString(z.UserId)
if err != nil {
err = msgp.WrapError(err, "UserId")
return
}
err = en.WriteString(z.Roles)
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
err = en.WriteInt64(z.DeleteAt)
if err != nil {
err = msgp.WrapError(err, "DeleteAt")
return
}
err = en.WriteBool(z.SchemeGuest)
if err != nil {
err = msgp.WrapError(err, "SchemeGuest")
return
}
err = en.WriteBool(z.SchemeUser)
if err != nil {
err = msgp.WrapError(err, "SchemeUser")
return
}
err = en.WriteBool(z.SchemeAdmin)
if err != nil {
err = msgp.WrapError(err, "SchemeAdmin")
return
}
err = en.WriteString(z.ExplicitRoles)
if err != nil {
err = msgp.WrapError(err, "ExplicitRoles")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *TeamMember) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 8
o = append(o, 0x98)
o = msgp.AppendString(o, z.TeamId)
o = msgp.AppendString(o, z.UserId)
o = msgp.AppendString(o, z.Roles)
o = msgp.AppendInt64(o, z.DeleteAt)
o = msgp.AppendBool(o, z.SchemeGuest)
o = msgp.AppendBool(o, z.SchemeUser)
o = msgp.AppendBool(o, z.SchemeAdmin)
o = msgp.AppendString(o, z.ExplicitRoles)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *TeamMember) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 8 {
err = msgp.ArrayError{Wanted: 8, Got: zb0001}
return
}
z.TeamId, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "TeamId")
return
}
z.UserId, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "UserId")
return
}
z.Roles, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
z.DeleteAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "DeleteAt")
return
}
z.SchemeGuest, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "SchemeGuest")
return
}
z.SchemeUser, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "SchemeUser")
return
}
z.SchemeAdmin, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "SchemeAdmin")
return
}
z.ExplicitRoles, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "ExplicitRoles")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *TeamMember) Msgsize() (s int) {
s = 1 + msgp.StringPrefixSize + len(z.TeamId) + msgp.StringPrefixSize + len(z.UserId) + msgp.StringPrefixSize + len(z.Roles) + msgp.Int64Size + msgp.BoolSize + msgp.BoolSize + msgp.BoolSize + msgp.StringPrefixSize + len(z.ExplicitRoles)
return
}

View File

@ -9,12 +9,17 @@ import (
)
type TeamSearch struct {
Term string `json:"term"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
AllowOpenInvite *bool `json:"allow_open_invite,omitempty"`
GroupConstrained *bool `json:"group_constrained,omitempty"`
IncludeGroupConstrained *bool `json:"include_group_constrained,omitempty"`
Term string `json:"term"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
AllowOpenInvite *bool `json:"allow_open_invite,omitempty"`
GroupConstrained *bool `json:"group_constrained,omitempty"`
IncludeGroupConstrained *bool `json:"include_group_constrained,omitempty"`
PolicyID *string `json:"policy_id,omitempty"`
ExcludePolicyConstrained *bool `json:"exclude_policy_constrained,omitempty"`
IncludePolicyID *bool `json:"-"`
IncludeDeleted *bool `json:"-"`
TeamType *string `json:"-"`
}
func (t *TeamSearch) IsPaginated() bool {

View File

@ -16,23 +16,24 @@ type Thread struct {
}
type ThreadResponse struct {
PostId string `json:"id"`
ReplyCount int64 `json:"reply_count"`
LastReplyAt int64 `json:"last_reply_at"`
LastViewedAt int64 `json:"last_viewed_at"`
Participants []*User `json:"participants"`
Post *Post `json:"post"`
PostId string `json:"id"`
ReplyCount int64 `json:"reply_count"`
LastReplyAt int64 `json:"last_reply_at"`
LastViewedAt int64 `json:"last_viewed_at"`
Participants []*User `json:"participants"`
Post *Post `json:"post"`
UnreadReplies int64 `json:"unread_replies"`
UnreadMentions int64 `json:"unread_mentions"`
}
type Threads struct {
Total int64 `json:"total"`
Threads []*ThreadResponse `json:"threads"`
Total int64 `json:"total"`
TotalUnreadThreads int64 `json:"total_unread_threads"`
TotalUnreadMentions int64 `json:"total_unread_mentions"`
Threads []*ThreadResponse `json:"threads"`
}
type GetUserThreadsOpts struct {
// Page specifies which part of the results to return, by PageSize. Default = 0
Page uint64
// PageSize specifies the size of the returned chunk of results. Default = 30
PageSize uint64
@ -44,6 +45,32 @@ type GetUserThreadsOpts struct {
// Since filters the threads based on their LastUpdateAt timestamp.
Since uint64
// Before specifies thread id as a cursor for pagination and will return `PageSize` threads before the cursor
Before string
// After specifies thread id as a cursor for pagination and will return `PageSize` threads after the cursor
After string
// Unread will make sure that only threads with unread replies are returned
Unread bool
// TotalsOnly will not fetch any threads and just fetch the total counts
TotalsOnly bool
// TeamOnly will only fetch threads and unreads for the specified team and excludes DMs/GMs
TeamOnly bool
}
func (o *ThreadResponse) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ThreadResponseFromJson(s string) (*ThreadResponse, error) {
var t ThreadResponse
err := json.Unmarshal([]byte(s), &t)
return &t, err
}
func (o *Threads) ToJson() string {
@ -56,6 +83,12 @@ func (o *Thread) ToJson() string {
return string(b)
}
func ThreadFromJson(s string) (*Thread, error) {
var t Thread
err := json.Unmarshal([]byte(s), &t)
return &t, err
}
func (o *Thread) Etag() string {
return Etag(o.PostId, o.LastReplyAt)
}

View File

@ -3,7 +3,9 @@
package model
import "net/http"
import (
"net/http"
)
const (
TOKEN_SIZE = 64

View File

@ -18,6 +18,9 @@ const (
UploadTypeImport UploadType = "import"
)
// UploadNoUserID is a "fake" user id used by the API layer when in local mode.
const UploadNoUserID = "nouser"
// UploadSession contains information used to keep track of a file upload.
type UploadSession struct {
// The unique identifier for the session.
@ -29,7 +32,7 @@ type UploadSession struct {
// The id of the user performing the upload.
UserId string `json:"user_id"`
// The id of the channel to upload to.
ChannelId string `json:"channel_id"`
ChannelId string `json:"channel_id,omitempty"`
// The name of the file to upload.
Filename string `json:"filename"`
// The path where the file is stored.
@ -39,6 +42,10 @@ type UploadSession struct {
// The amount of received data in bytes. If equal to FileSize it means the
// upload has finished.
FileOffset int64 `json:"file_offset"`
// Id of remote cluster if uploading for shared channel
RemoteId string `json:"remote_id"`
// Requested file id if uploading for shared channel
ReqFileId string `json:"req_file_id"`
}
// ToJson serializes the UploadSession into JSON and returns it as string.
@ -109,7 +116,7 @@ func (us *UploadSession) IsValid() *AppError {
return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.type.app_error", nil, err.Error(), http.StatusBadRequest)
}
if !IsValidId(us.UserId) {
if !IsValidId(us.UserId) && us.UserId != UploadNoUserID {
return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.user_id.app_error", nil, "id="+us.Id, http.StatusBadRequest)
}

View File

@ -9,17 +9,17 @@ import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
"github.com/mattermost/mattermost-server/v5/services/timezones"
"golang.org/x/crypto/bcrypt"
"golang.org/x/text/language"
"github.com/mattermost/mattermost-server/v5/services/timezones"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
const (
@ -57,6 +57,7 @@ const (
USER_NAME_MIN_LENGTH = 1
USER_PASSWORD_MAX_LENGTH = 72
USER_LOCALE_MAX_LENGTH = 5
USER_TIMEZONE_MAX_RUNES = 256
)
//msgp:tuple User
@ -90,12 +91,14 @@ type User struct {
Timezone StringMap `json:"timezone"`
MfaActive bool `json:"mfa_active,omitempty"`
MfaSecret string `json:"mfa_secret,omitempty"`
RemoteId *string `json:"remote_id,omitempty"`
LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
IsBot bool `db:"-" json:"is_bot,omitempty"`
BotDescription string `db:"-" json:"bot_description,omitempty"`
BotLastIconUpdate int64 `db:"-" json:"bot_last_icon_update,omitempty"`
TermsOfServiceId string `db:"-" json:"terms_of_service_id,omitempty"`
TermsOfServiceCreateAt int64 `db:"-" json:"terms_of_service_create_at,omitempty"`
DisableWelcomeEmail bool `db:"-" json:"disable_welcome_email"`
}
//msgp UserMap
@ -104,11 +107,13 @@ type User struct {
// It is used to generate methods which can be used for fast serialization/de-serialization.
type UserMap map[string]*User
//msgp:ignore UserUpdate
type UserUpdate struct {
Old *User
New *User
}
//msgp:ignore UserPatch
type UserPatch struct {
Username *string `json:"username"`
Password *string `json:"password,omitempty"`
@ -121,14 +126,17 @@ type UserPatch struct {
NotifyProps StringMap `json:"notify_props,omitempty"`
Locale *string `json:"locale"`
Timezone StringMap `json:"timezone"`
RemoteId *string `json:"remote_id"`
}
//msgp:ignore UserAuth
type UserAuth struct {
Password string `json:"password,omitempty"`
Password string `json:"password,omitempty"` // DEPRECATED: It is not used.
AuthData *string `json:"auth_data,omitempty"`
AuthService string `json:"auth_service,omitempty"`
}
//msgp:ignore UserForIndexing
type UserForIndexing struct {
Id string `json:"id"`
Username string `json:"username"`
@ -142,6 +150,7 @@ type UserForIndexing struct {
ChannelsIds []string `json:"channel_id"`
}
//msgp:ignore ViewUsersRestrictions
type ViewUsersRestrictions struct {
Teams []string
Channels []string
@ -158,6 +167,7 @@ func (r *ViewUsersRestrictions) Hash() string {
return fmt.Sprintf("%x", hash.Sum(nil))
}
//msgp:ignore UserSlice
type UserSlice []*User
func (u UserSlice) Usernames() []string {
@ -262,11 +272,17 @@ func (u *User) IsValid() *AppError {
return InvalidUserError("update_at", u.Id)
}
if !IsValidUsername(u.Username) {
return InvalidUserError("username", u.Id)
if u.IsRemote() {
if !IsValidUsernameAllowRemote(u.Username) {
return InvalidUserError("username", u.Id)
}
} else {
if !IsValidUsername(u.Username) {
return InvalidUserError("username", u.Id)
}
}
if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 || !IsValidEmail(u.Email) {
if len(u.Email) > USER_EMAIL_MAX_LENGTH || u.Email == "" || !IsValidEmail(u.Email) {
return InvalidUserError("email", u.Id)
}
@ -290,11 +306,11 @@ func (u *User) IsValid() *AppError {
return InvalidUserError("auth_data", u.Id)
}
if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 {
if u.AuthData != nil && *u.AuthData != "" && u.AuthService == "" {
return InvalidUserError("auth_data_type", u.Id)
}
if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 {
if u.Password != "" && u.AuthData != nil && *u.AuthData != "" {
return InvalidUserError("auth_data_pwd", u.Id)
}
@ -306,6 +322,14 @@ func (u *User) IsValid() *AppError {
return InvalidUserError("locale", u.Id)
}
if len(u.Timezone) > 0 {
if tzJSON, err := json.Marshal(u.Timezone); err != nil {
return NewAppError("User.IsValid", "model.user.is_valid.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
} else if utf8.RuneCount(tzJSON) > USER_TIMEZONE_MAX_RUNES {
return InvalidUserError("timezone_limit", u.Id)
}
}
return nil
}
@ -373,7 +397,7 @@ func (u *User) PreSave() {
u.Timezone = timezones.DefaultUserTimezone()
}
if len(u.Password) > 0 {
if u.Password != "" {
u.Password = HashPassword(u.Password)
}
}
@ -406,7 +430,7 @@ func (u *User) PreUpdate() {
splitKeys := strings.Split(u.NotifyProps[MENTION_KEYS_NOTIFY_PROP], ",")
goodKeys := []string{}
for _, key := range splitKeys {
if len(key) > 0 {
if key != "" {
goodKeys = append(goodKeys, strings.ToLower(key))
}
}
@ -497,6 +521,10 @@ func (u *User) Patch(patch *UserPatch) {
if patch.Timezone != nil {
u.Timezone = patch.Timezone
}
if patch.RemoteId != nil {
u.RemoteId = patch.RemoteId
}
}
// ToJson convert a User to a json string
@ -553,6 +581,7 @@ func (u *User) SanitizeInput(isAdmin bool) {
u.FailedAttempts = 0
u.MfaActive = false
u.MfaSecret = ""
u.Email = strings.TrimSpace(u.Email)
}
func (u *User) ClearNonProfileFields() {
@ -588,12 +617,22 @@ func (u *User) AddNotifyProp(key string, value string) {
u.NotifyProps[key] = value
}
func (u *User) SetCustomStatus(cs *CustomStatus) {
u.MakeNonNil()
u.Props[UserPropsKeyCustomStatus] = cs.ToJson()
}
func (u *User) ClearCustomStatus() {
u.MakeNonNil()
u.Props[UserPropsKeyCustomStatus] = ""
}
func (u *User) GetFullName() string {
if len(u.FirstName) > 0 && len(u.LastName) > 0 {
if u.FirstName != "" && u.LastName != "" {
return u.FirstName + " " + u.LastName
} else if len(u.FirstName) > 0 {
} else if u.FirstName != "" {
return u.FirstName
} else if len(u.LastName) > 0 {
} else if u.LastName != "" {
return u.LastName
} else {
return ""
@ -604,13 +643,13 @@ func (u *User) getDisplayName(baseName, nameFormat string) string {
displayName := baseName
if nameFormat == SHOW_NICKNAME_FULLNAME {
if len(u.Nickname) > 0 {
if u.Nickname != "" {
displayName = u.Nickname
} else if fullName := u.GetFullName(); len(fullName) > 0 {
} else if fullName := u.GetFullName(); fullName != "" {
displayName = fullName
}
} else if nameFormat == SHOW_FULLNAME {
if fullName := u.GetFullName(); len(fullName) > 0 {
if fullName := u.GetFullName(); fullName != "" {
displayName = fullName
}
}
@ -691,7 +730,10 @@ func (u *User) IsSSOUser() bool {
}
func (u *User) IsOAuthUser() bool {
return u.AuthService == USER_AUTH_SERVICE_GITLAB
return u.AuthService == SERVICE_GITLAB ||
u.AuthService == SERVICE_GOOGLE ||
u.AuthService == SERVICE_OFFICE365 ||
u.AuthService == SERVICE_OPENID
}
func (u *User) IsLDAPUser() bool {
@ -706,6 +748,61 @@ func (u *User) GetPreferredTimezone() string {
return GetPreferredTimezone(u.Timezone)
}
// IsRemote returns true if the user belongs to a remote cluster (has RemoteId).
func (u *User) IsRemote() bool {
return u.RemoteId != nil && *u.RemoteId != ""
}
// GetRemoteID returns the remote id for this user or "" if not a remote user.
func (u *User) GetRemoteID() string {
if u.RemoteId != nil {
return *u.RemoteId
}
return ""
}
// GetProp fetches a prop value by name.
func (u *User) GetProp(name string) (string, bool) {
val, ok := u.Props[name]
return val, ok
}
// SetProp sets a prop value by name, creating the map if nil.
// Not thread safe.
func (u *User) SetProp(name string, value string) {
if u.Props == nil {
u.Props = make(map[string]string)
}
u.Props[name] = value
}
func (u *User) ToPatch() *UserPatch {
return &UserPatch{
Username: &u.Username, Password: &u.Password,
Nickname: &u.Nickname, FirstName: &u.FirstName, LastName: &u.LastName,
Position: &u.Position, Email: &u.Email,
Props: u.Props, NotifyProps: u.NotifyProps,
Locale: &u.Locale, Timezone: u.Timezone,
}
}
func (u *UserPatch) SetField(fieldName string, fieldValue string) {
switch fieldName {
case "FirstName":
u.FirstName = &fieldValue
case "LastName":
u.LastName = &fieldValue
case "Nickname":
u.Nickname = &fieldValue
case "Email":
u.Email = &fieldValue
case "Position":
u.Position = &fieldValue
case "Username":
u.Username = &fieldValue
}
}
// UserFromJson will decode the input and return a User
func UserFromJson(data io.Reader) *User {
var user *User
@ -758,9 +855,10 @@ func HashPassword(password string) string {
}
// ComparePassword compares the hash
// This function is deprecated and will be removed in a future release.
func ComparePassword(hash string, password string) bool {
if len(password) == 0 || len(hash) == 0 {
if password == "" || hash == "" {
return false
}
@ -769,12 +867,13 @@ func ComparePassword(hash string, password string) bool {
}
var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
var validUsernameCharsForRemote = regexp.MustCompile(`^[a-z0-9\.\-_:]+$`)
var restrictedUsernames = []string{
"all",
"channel",
"matterbot",
"system",
var restrictedUsernames = map[string]struct{}{
"all": {},
"channel": {},
"matterbot": {},
"system": {},
}
func IsValidUsername(s string) bool {
@ -786,17 +885,25 @@ func IsValidUsername(s string) bool {
return false
}
for _, restrictedUsername := range restrictedUsernames {
if s == restrictedUsername {
return false
}
}
return true
_, found := restrictedUsernames[s]
return !found
}
func CleanUsername(s string) string {
s = NormalizeUsername(strings.Replace(s, " ", "-", -1))
func IsValidUsernameAllowRemote(s string) bool {
if len(s) < USER_NAME_MIN_LENGTH || len(s) > USER_NAME_MAX_LENGTH {
return false
}
if !validUsernameCharsForRemote.MatchString(s) {
return false
}
_, found := restrictedUsernames[s]
return !found
}
func CleanUsername(username string) string {
s := NormalizeUsername(strings.Replace(username, " ", "-", -1))
for _, value := range reservedName {
if s == value {
@ -817,6 +924,8 @@ func CleanUsername(s string) string {
if !IsValidUsername(s) {
s = "a" + NewId()
mlog.Warn("Generating new username since provided username was invalid",
mlog.String("provided_username", username), mlog.String("new_username", s))
}
return s
@ -858,6 +967,7 @@ func IsValidLocale(locale string) bool {
return true
}
//msgp:ignore UserWithGroups
type UserWithGroups struct {
User
GroupIDs *string `json:"-"`
@ -872,12 +982,13 @@ func (u *UserWithGroups) GetGroupIDs() []string {
return nil
}
trimmed := strings.TrimSpace(*u.GroupIDs)
if len(trimmed) == 0 {
if trimmed == "" {
return nil
}
return strings.Split(trimmed, ",")
}
//msgp:ignore UsersWithGroupsAndCount
type UsersWithGroupsAndCount struct {
Users []*UserWithGroups `json:"users"`
Count int64 `json:"total_count"`
@ -889,27 +1000,3 @@ func UsersWithGroupsAndCountFromJson(data io.Reader) *UsersWithGroupsAndCount {
json.Unmarshal(bodyBytes, uwg)
return uwg
}
var passwordRandomSource = rand.NewSource(time.Now().Unix())
var passwordSpecialChars = "!$%^&*(),."
var passwordNumbers = "0123456789"
var passwordUpperCaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var passwordLowerCaseLetters = "abcdefghijklmnopqrstuvwxyz"
var passwordAllChars = passwordSpecialChars + passwordNumbers + passwordUpperCaseLetters + passwordLowerCaseLetters
func GeneratePassword(minimumLength int) string {
r := rand.New(passwordRandomSource)
// Make sure we are guaranteed at least one of each type to meet any possible password complexity requirements.
password := string([]rune(passwordUpperCaseLetters)[r.Intn(len(passwordUpperCaseLetters))]) +
string([]rune(passwordNumbers)[r.Intn(len(passwordNumbers))]) +
string([]rune(passwordLowerCaseLetters)[r.Intn(len(passwordLowerCaseLetters))]) +
string([]rune(passwordSpecialChars)[r.Intn(len(passwordSpecialChars))])
for len(password) < minimumLength {
i := r.Intn(len(passwordAllChars))
password = password + string([]rune(passwordAllChars)[i])
}
return password
}

View File

@ -31,11 +31,10 @@ func UserAutocompleteFromJson(data io.Reader) *UserAutocomplete {
decoder := json.NewDecoder(data)
autocomplete := new(UserAutocomplete)
err := decoder.Decode(&autocomplete)
if err == nil {
return autocomplete
} else {
if err != nil {
return nil
}
return autocomplete
}
func (o *UserAutocompleteInChannel) ToJson() string {

View File

@ -0,0 +1,826 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *User) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 32 {
err = msgp.ArrayError{Wanted: 32, Got: zb0001}
return
}
z.Id, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
z.CreateAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "CreateAt")
return
}
z.UpdateAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "UpdateAt")
return
}
z.DeleteAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "DeleteAt")
return
}
z.Username, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Username")
return
}
z.Password, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Password")
return
}
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
err = msgp.WrapError(err, "AuthData")
return
}
z.AuthData = nil
} else {
if z.AuthData == nil {
z.AuthData = new(string)
}
*z.AuthData, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "AuthData")
return
}
}
z.AuthService, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "AuthService")
return
}
z.Email, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Email")
return
}
z.EmailVerified, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "EmailVerified")
return
}
z.Nickname, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Nickname")
return
}
z.FirstName, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "FirstName")
return
}
z.LastName, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "LastName")
return
}
z.Position, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Position")
return
}
z.Roles, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
z.AllowMarketing, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "AllowMarketing")
return
}
err = z.Props.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
err = z.NotifyProps.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "NotifyProps")
return
}
z.LastPasswordUpdate, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "LastPasswordUpdate")
return
}
z.LastPictureUpdate, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "LastPictureUpdate")
return
}
z.FailedAttempts, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "FailedAttempts")
return
}
z.Locale, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Locale")
return
}
err = z.Timezone.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "Timezone")
return
}
z.MfaActive, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "MfaActive")
return
}
z.MfaSecret, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "MfaSecret")
return
}
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
err = msgp.WrapError(err, "RemoteId")
return
}
z.RemoteId = nil
} else {
if z.RemoteId == nil {
z.RemoteId = new(string)
}
*z.RemoteId, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "RemoteId")
return
}
}
z.LastActivityAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "LastActivityAt")
return
}
z.IsBot, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "IsBot")
return
}
z.BotDescription, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "BotDescription")
return
}
z.BotLastIconUpdate, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "BotLastIconUpdate")
return
}
z.TermsOfServiceId, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "TermsOfServiceId")
return
}
z.TermsOfServiceCreateAt, err = dc.ReadInt64()
if err != nil {
err = msgp.WrapError(err, "TermsOfServiceCreateAt")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *User) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 32
err = en.Append(0xdc, 0x0, 0x20)
if err != nil {
return
}
err = en.WriteString(z.Id)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
err = en.WriteInt64(z.CreateAt)
if err != nil {
err = msgp.WrapError(err, "CreateAt")
return
}
err = en.WriteInt64(z.UpdateAt)
if err != nil {
err = msgp.WrapError(err, "UpdateAt")
return
}
err = en.WriteInt64(z.DeleteAt)
if err != nil {
err = msgp.WrapError(err, "DeleteAt")
return
}
err = en.WriteString(z.Username)
if err != nil {
err = msgp.WrapError(err, "Username")
return
}
err = en.WriteString(z.Password)
if err != nil {
err = msgp.WrapError(err, "Password")
return
}
if z.AuthData == nil {
err = en.WriteNil()
if err != nil {
return
}
} else {
err = en.WriteString(*z.AuthData)
if err != nil {
err = msgp.WrapError(err, "AuthData")
return
}
}
err = en.WriteString(z.AuthService)
if err != nil {
err = msgp.WrapError(err, "AuthService")
return
}
err = en.WriteString(z.Email)
if err != nil {
err = msgp.WrapError(err, "Email")
return
}
err = en.WriteBool(z.EmailVerified)
if err != nil {
err = msgp.WrapError(err, "EmailVerified")
return
}
err = en.WriteString(z.Nickname)
if err != nil {
err = msgp.WrapError(err, "Nickname")
return
}
err = en.WriteString(z.FirstName)
if err != nil {
err = msgp.WrapError(err, "FirstName")
return
}
err = en.WriteString(z.LastName)
if err != nil {
err = msgp.WrapError(err, "LastName")
return
}
err = en.WriteString(z.Position)
if err != nil {
err = msgp.WrapError(err, "Position")
return
}
err = en.WriteString(z.Roles)
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
err = en.WriteBool(z.AllowMarketing)
if err != nil {
err = msgp.WrapError(err, "AllowMarketing")
return
}
err = z.Props.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
err = z.NotifyProps.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "NotifyProps")
return
}
err = en.WriteInt64(z.LastPasswordUpdate)
if err != nil {
err = msgp.WrapError(err, "LastPasswordUpdate")
return
}
err = en.WriteInt64(z.LastPictureUpdate)
if err != nil {
err = msgp.WrapError(err, "LastPictureUpdate")
return
}
err = en.WriteInt(z.FailedAttempts)
if err != nil {
err = msgp.WrapError(err, "FailedAttempts")
return
}
err = en.WriteString(z.Locale)
if err != nil {
err = msgp.WrapError(err, "Locale")
return
}
err = z.Timezone.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "Timezone")
return
}
err = en.WriteBool(z.MfaActive)
if err != nil {
err = msgp.WrapError(err, "MfaActive")
return
}
err = en.WriteString(z.MfaSecret)
if err != nil {
err = msgp.WrapError(err, "MfaSecret")
return
}
if z.RemoteId == nil {
err = en.WriteNil()
if err != nil {
return
}
} else {
err = en.WriteString(*z.RemoteId)
if err != nil {
err = msgp.WrapError(err, "RemoteId")
return
}
}
err = en.WriteInt64(z.LastActivityAt)
if err != nil {
err = msgp.WrapError(err, "LastActivityAt")
return
}
err = en.WriteBool(z.IsBot)
if err != nil {
err = msgp.WrapError(err, "IsBot")
return
}
err = en.WriteString(z.BotDescription)
if err != nil {
err = msgp.WrapError(err, "BotDescription")
return
}
err = en.WriteInt64(z.BotLastIconUpdate)
if err != nil {
err = msgp.WrapError(err, "BotLastIconUpdate")
return
}
err = en.WriteString(z.TermsOfServiceId)
if err != nil {
err = msgp.WrapError(err, "TermsOfServiceId")
return
}
err = en.WriteInt64(z.TermsOfServiceCreateAt)
if err != nil {
err = msgp.WrapError(err, "TermsOfServiceCreateAt")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *User) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 32
o = append(o, 0xdc, 0x0, 0x20)
o = msgp.AppendString(o, z.Id)
o = msgp.AppendInt64(o, z.CreateAt)
o = msgp.AppendInt64(o, z.UpdateAt)
o = msgp.AppendInt64(o, z.DeleteAt)
o = msgp.AppendString(o, z.Username)
o = msgp.AppendString(o, z.Password)
if z.AuthData == nil {
o = msgp.AppendNil(o)
} else {
o = msgp.AppendString(o, *z.AuthData)
}
o = msgp.AppendString(o, z.AuthService)
o = msgp.AppendString(o, z.Email)
o = msgp.AppendBool(o, z.EmailVerified)
o = msgp.AppendString(o, z.Nickname)
o = msgp.AppendString(o, z.FirstName)
o = msgp.AppendString(o, z.LastName)
o = msgp.AppendString(o, z.Position)
o = msgp.AppendString(o, z.Roles)
o = msgp.AppendBool(o, z.AllowMarketing)
o, err = z.Props.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
o, err = z.NotifyProps.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "NotifyProps")
return
}
o = msgp.AppendInt64(o, z.LastPasswordUpdate)
o = msgp.AppendInt64(o, z.LastPictureUpdate)
o = msgp.AppendInt(o, z.FailedAttempts)
o = msgp.AppendString(o, z.Locale)
o, err = z.Timezone.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "Timezone")
return
}
o = msgp.AppendBool(o, z.MfaActive)
o = msgp.AppendString(o, z.MfaSecret)
if z.RemoteId == nil {
o = msgp.AppendNil(o)
} else {
o = msgp.AppendString(o, *z.RemoteId)
}
o = msgp.AppendInt64(o, z.LastActivityAt)
o = msgp.AppendBool(o, z.IsBot)
o = msgp.AppendString(o, z.BotDescription)
o = msgp.AppendInt64(o, z.BotLastIconUpdate)
o = msgp.AppendString(o, z.TermsOfServiceId)
o = msgp.AppendInt64(o, z.TermsOfServiceCreateAt)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *User) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 32 {
err = msgp.ArrayError{Wanted: 32, Got: zb0001}
return
}
z.Id, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
z.CreateAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "CreateAt")
return
}
z.UpdateAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "UpdateAt")
return
}
z.DeleteAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "DeleteAt")
return
}
z.Username, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Username")
return
}
z.Password, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Password")
return
}
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
if err != nil {
return
}
z.AuthData = nil
} else {
if z.AuthData == nil {
z.AuthData = new(string)
}
*z.AuthData, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AuthData")
return
}
}
z.AuthService, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AuthService")
return
}
z.Email, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Email")
return
}
z.EmailVerified, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "EmailVerified")
return
}
z.Nickname, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Nickname")
return
}
z.FirstName, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "FirstName")
return
}
z.LastName, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LastName")
return
}
z.Position, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Position")
return
}
z.Roles, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Roles")
return
}
z.AllowMarketing, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllowMarketing")
return
}
bts, err = z.Props.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Props")
return
}
bts, err = z.NotifyProps.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "NotifyProps")
return
}
z.LastPasswordUpdate, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "LastPasswordUpdate")
return
}
z.LastPictureUpdate, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "LastPictureUpdate")
return
}
z.FailedAttempts, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "FailedAttempts")
return
}
z.Locale, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Locale")
return
}
bts, err = z.Timezone.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Timezone")
return
}
z.MfaActive, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "MfaActive")
return
}
z.MfaSecret, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "MfaSecret")
return
}
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
if err != nil {
return
}
z.RemoteId = nil
} else {
if z.RemoteId == nil {
z.RemoteId = new(string)
}
*z.RemoteId, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "RemoteId")
return
}
}
z.LastActivityAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "LastActivityAt")
return
}
z.IsBot, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "IsBot")
return
}
z.BotDescription, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "BotDescription")
return
}
z.BotLastIconUpdate, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "BotLastIconUpdate")
return
}
z.TermsOfServiceId, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "TermsOfServiceId")
return
}
z.TermsOfServiceCreateAt, bts, err = msgp.ReadInt64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "TermsOfServiceCreateAt")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *User) Msgsize() (s int) {
s = 3 + msgp.StringPrefixSize + len(z.Id) + msgp.Int64Size + msgp.Int64Size + msgp.Int64Size + msgp.StringPrefixSize + len(z.Username) + msgp.StringPrefixSize + len(z.Password)
if z.AuthData == nil {
s += msgp.NilSize
} else {
s += msgp.StringPrefixSize + len(*z.AuthData)
}
s += msgp.StringPrefixSize + len(z.AuthService) + msgp.StringPrefixSize + len(z.Email) + msgp.BoolSize + msgp.StringPrefixSize + len(z.Nickname) + msgp.StringPrefixSize + len(z.FirstName) + msgp.StringPrefixSize + len(z.LastName) + msgp.StringPrefixSize + len(z.Position) + msgp.StringPrefixSize + len(z.Roles) + msgp.BoolSize + z.Props.Msgsize() + z.NotifyProps.Msgsize() + msgp.Int64Size + msgp.Int64Size + msgp.IntSize + msgp.StringPrefixSize + len(z.Locale) + z.Timezone.Msgsize() + msgp.BoolSize + msgp.StringPrefixSize + len(z.MfaSecret)
if z.RemoteId == nil {
s += msgp.NilSize
} else {
s += msgp.StringPrefixSize + len(*z.RemoteId)
}
s += msgp.Int64Size + msgp.BoolSize + msgp.StringPrefixSize + len(z.BotDescription) + msgp.Int64Size + msgp.StringPrefixSize + len(z.TermsOfServiceId) + msgp.Int64Size
return
}
// DecodeMsg implements msgp.Decodable
func (z *UserMap) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0003 uint32
zb0003, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if (*z) == nil {
(*z) = make(UserMap, zb0003)
} else if len((*z)) > 0 {
for key := range *z {
delete((*z), key)
}
}
for zb0003 > 0 {
zb0003--
var zb0001 string
var zb0002 *User
zb0001, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err)
return
}
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
zb0002 = nil
} else {
if zb0002 == nil {
zb0002 = new(User)
}
err = zb0002.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
}
(*z)[zb0001] = zb0002
}
return
}
// EncodeMsg implements msgp.Encodable
func (z UserMap) EncodeMsg(en *msgp.Writer) (err error) {
err = en.WriteMapHeader(uint32(len(z)))
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0004, zb0005 := range z {
err = en.WriteString(zb0004)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0005 == nil {
err = en.WriteNil()
if err != nil {
return
}
} else {
err = zb0005.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, zb0004)
return
}
}
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z UserMap) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendMapHeader(o, uint32(len(z)))
for zb0004, zb0005 := range z {
o = msgp.AppendString(o, zb0004)
if zb0005 == nil {
o = msgp.AppendNil(o)
} else {
o, err = zb0005.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, zb0004)
return
}
}
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *UserMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0003 uint32
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if (*z) == nil {
(*z) = make(UserMap, zb0003)
} else if len((*z)) > 0 {
for key := range *z {
delete((*z), key)
}
}
for zb0003 > 0 {
var zb0001 string
var zb0002 *User
zb0003--
zb0001, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
if err != nil {
return
}
zb0002 = nil
} else {
if zb0002 == nil {
zb0002 = new(User)
}
bts, err = zb0002.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, zb0001)
return
}
}
(*z)[zb0001] = zb0002
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z UserMap) Msgsize() (s int) {
s = msgp.MapHeaderSize
if z != nil {
for zb0004, zb0005 := range z {
_ = zb0005
s += msgp.StringPrefixSize + len(zb0004)
if zb0005 == nil {
s += msgp.NilSize
} else {
s += zb0005.Msgsize()
}
}
}
return
}

View File

@ -18,10 +18,11 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
"unicode"
goi18n "github.com/mattermost/go-i18n/i18n"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/pborman/uuid"
)
@ -30,10 +31,10 @@ const (
UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
NUMBERS = "0123456789"
SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
MB = 1 << 20
)
type StringInterface map[string]interface{}
type StringMap map[string]string
type StringArray []string
func (sa StringArray) Remove(input string) StringArray {
@ -72,10 +73,13 @@ func (sa StringArray) Equals(input StringArray) bool {
return true
}
var translateFunc goi18n.TranslateFunc = nil
var translateFunc i18n.TranslateFunc
var translateFuncOnce sync.Once
func AppErrorInit(t goi18n.TranslateFunc) {
translateFunc = t
func AppErrorInit(t i18n.TranslateFunc) {
translateFuncOnce.Do(func() {
translateFunc = t
})
}
type AppError struct {
@ -93,7 +97,7 @@ func (er *AppError) Error() string {
return er.Where + ": " + er.Message + ", " + er.DetailedError
}
func (er *AppError) Translate(T goi18n.TranslateFunc) {
func (er *AppError) Translate(T i18n.TranslateFunc) {
if T == nil {
er.Message = er.Id
return
@ -106,12 +110,11 @@ func (er *AppError) Translate(T goi18n.TranslateFunc) {
}
}
func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string {
func (er *AppError) SystemMessage(T i18n.TranslateFunc) string {
if er.params == nil {
return T(er.Id)
} else {
return T(er.Id, er.params)
}
return T(er.Id, er.params)
}
func (er *AppError) ToJson() string {
@ -132,11 +135,10 @@ func AppErrorFromJson(data io.Reader) *AppError {
decoder := json.NewDecoder(strings.NewReader(str))
var er AppError
err := decoder.Decode(&er)
if err == nil {
return &er
} else {
if err != nil {
return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError)
}
return &er
}
func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError {
@ -183,14 +185,6 @@ func NewRandomString(length int) string {
return encoding.EncodeToString(data)[:length]
}
// NewRandomBase32String returns a base32 encoded string of a random slice
// of bytes of the given size. The resulting entropy will be (8 * size) bits.
func NewRandomBase32String(size int) string {
data := make([]byte, size)
rand.Read(data)
return base32.StdEncoding.EncodeToString(data)
}
// GetMillis is a convenience method to get milliseconds since epoch.
func GetMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
@ -201,6 +195,11 @@ func GetMillisForTime(thisTime time.Time) int64 {
return thisTime.UnixNano() / int64(time.Millisecond)
}
// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.
func GetTimeForMillis(millis int64) time.Time {
return time.Unix(0, millis*int64(time.Millisecond))
}
// PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format
func PadDateStringZeros(dateString string) string {
parts := strings.Split(dateString, "-")
@ -254,9 +253,8 @@ func MapFromJson(data io.Reader) map[string]string {
var objmap map[string]string
if err := decoder.Decode(&objmap); err != nil {
return make(map[string]string)
} else {
return objmap
}
return objmap
}
// MapFromJson will decode the key/value pair map
@ -266,9 +264,8 @@ func MapBoolFromJson(data io.Reader) map[string]bool {
var objmap map[string]bool
if err := decoder.Decode(&objmap); err != nil {
return make(map[string]bool)
} else {
return objmap
}
return objmap
}
func ArrayToJson(objmap []string) string {
@ -282,9 +279,8 @@ func ArrayFromJson(data io.Reader) []string {
var objmap []string
if err := decoder.Decode(&objmap); err != nil {
return make([]string, 0)
} else {
return objmap
}
return objmap
}
func ArrayFromInterface(data interface{}) []string {
@ -315,9 +311,8 @@ func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
var objmap map[string]interface{}
if err := decoder.Decode(&objmap); err != nil {
return make(map[string]interface{})
} else {
return objmap
}
return objmap
}
func StringToJson(s string) string {
@ -331,14 +326,19 @@ func StringFromJson(data io.Reader) string {
var s string
if err := decoder.Decode(&s); err != nil {
return ""
} else {
return s
}
return s
}
// ToJson serializes an arbitrary data type to JSON, discarding the error.
func ToJson(v interface{}) []byte {
b, _ := json.Marshal(v)
return b
}
func GetServerIpAddress(iface string) string {
var addrs []net.Addr
if len(iface) == 0 {
if iface == "" {
var err error
addrs, err = net.InterfaceAddrs()
if err != nil {
@ -397,6 +397,7 @@ var reservedName = []string{
"channel",
"claim",
"error",
"files",
"help",
"landing",
"login",
@ -437,6 +438,12 @@ func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
}
func IsValidAlphaNumHyphenUnderscorePlus(s string) bool {
validSimpleAlphaNumHyphenUnderscorePlus := regexp.MustCompile(`^[a-zA-Z0-9+_-]+$`)
return validSimpleAlphaNumHyphenUnderscorePlus.MatchString(s)
}
func Etag(parts ...interface{}) string {
etag := CurrentVersion
@ -486,25 +493,6 @@ func ParseHashtags(text string) (string, string) {
return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
}
func IsFileExtImage(ext string) bool {
ext = strings.ToLower(ext)
for _, imgExt := range IMAGE_EXTENSIONS {
if ext == imgExt {
return true
}
}
return false
}
func GetImageMimeType(ext string) string {
ext = strings.ToLower(ext)
if len(IMAGE_MIME_TYPES[ext]) == 0 {
return "image"
} else {
return IMAGE_MIME_TYPES[ext]
}
}
func ClearMentionTags(post string) string {
post = strings.Replace(post, "<mention>", "", -1)
post = strings.Replace(post, "</mention>", "", -1)
@ -721,3 +709,18 @@ func filterBlocklist(r rune) rune {
return r
}
// UniqueStrings returns a unique subset of the string slice provided.
func UniqueStrings(input []string) []string {
u := make([]string, 0, len(input))
m := make(map[string]bool)
for _, val := range input {
if _, ok := m[val]; !ok {
m[val] = true
u = append(u, val)
}
}
return u
}

View File

@ -13,6 +13,17 @@ import (
// It should be maintained in chronological order with most current
// release at the front of the list.
var versions = []string{
"5.39.0",
"5.38.2",
"5.38.1",
"5.38.0",
"5.37.0",
"5.36.0",
"5.35.0",
"5.34.0",
"5.33.0",
"5.32.0",
"5.31.0",
"5.30.0",
"5.29.0",
"5.28.0",
@ -148,9 +159,8 @@ func IsCurrentVersion(versionToCheck string) bool {
if toCheckMajor == currentMajor && toCheckMinor == currentMinor {
return true
} else {
return false
}
return false
}
func IsPreviousVersionsSupported(versionToCheck string) bool {

View File

@ -70,6 +70,10 @@ const (
WEBSOCKET_WARN_METRIC_STATUS_RECEIVED = "warn_metric_status_received"
WEBSOCKET_WARN_METRIC_STATUS_REMOVED = "warn_metric_status_removed"
WEBSOCKET_EVENT_CLOUD_PAYMENT_STATUS_UPDATED = "cloud_payment_status_updated"
WEBSOCKET_EVENT_THREAD_UPDATED = "thread_updated"
WEBSOCKET_EVENT_THREAD_FOLLOW_CHANGED = "thread_follow_changed"
WEBSOCKET_EVENT_THREAD_READ_CHANGED = "thread_read_changed"
WEBSOCKET_FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED = "first_admin_visit_marketplace_status_received"
)
type WebSocketMessage interface {

View File

@ -7,7 +7,7 @@ import (
"encoding/json"
"io"
goi18n "github.com/mattermost/go-i18n/i18n"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
)
// WebSocketRequest represents a request made to the server through a websocket.
@ -18,9 +18,9 @@ type WebSocketRequest struct {
Data map[string]interface{} `json:"data"` // The metadata for an action.
// Server-provided fields
Session Session `json:"-"`
T goi18n.TranslateFunc `json:"-"`
Locale string `json:"-"`
Session Session `json:"-"`
T i18n.TranslateFunc `json:"-"`
Locale string `json:"-"`
}
func (o *WebSocketRequest) ToJson() string {

View File

@ -0,0 +1,83 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package filestore
import (
"io"
"time"
"github.com/pkg/errors"
)
const (
driverS3 = "amazons3"
driverLocal = "local"
)
type ReadCloseSeeker interface {
io.ReadCloser
io.Seeker
}
type FileBackend interface {
TestConnection() error
Reader(path string) (ReadCloseSeeker, error)
ReadFile(path string) ([]byte, error)
FileExists(path string) (bool, error)
FileSize(path string) (int64, error)
CopyFile(oldPath, newPath string) error
MoveFile(oldPath, newPath string) error
WriteFile(fr io.Reader, path string) (int64, error)
AppendFile(fr io.Reader, path string) (int64, error)
RemoveFile(path string) error
FileModTime(path string) (time.Time, error)
ListDirectory(path string) ([]string, error)
RemoveDirectory(path string) error
}
type FileBackendSettings struct {
DriverName string
Directory string
AmazonS3AccessKeyId string
AmazonS3SecretAccessKey string
AmazonS3Bucket string
AmazonS3PathPrefix string
AmazonS3Region string
AmazonS3Endpoint string
AmazonS3SSL bool
AmazonS3SignV2 bool
AmazonS3SSE bool
AmazonS3Trace bool
}
func (settings *FileBackendSettings) CheckMandatoryS3Fields() error {
if settings.AmazonS3Bucket == "" {
return errors.New("missing s3 bucket settings")
}
// if S3 endpoint is not set call the set defaults to set that
if settings.AmazonS3Endpoint == "" {
settings.AmazonS3Endpoint = "s3.amazonaws.com"
}
return nil
}
func NewFileBackend(settings FileBackendSettings) (FileBackend, error) {
switch settings.DriverName {
case driverS3:
backend, err := NewS3FileBackend(settings)
if err != nil {
return nil, errors.Wrap(err, "unable to connect to the s3 backend")
}
return backend, nil
case driverLocal:
return &LocalFileBackend{
directory: settings.Directory,
}, nil
}
return nil, errors.New("no valid filestorage driver found")
}

View File

@ -0,0 +1,211 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package filestore
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
const (
TestFilePath = "/testfile"
)
type LocalFileBackend struct {
directory string
}
// copyFile will copy a file from src path to dst path.
// Overwrites any existing files at dst.
// Permissions are copied from file at src to the new file at dst.
func copyFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
return
}
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
if e := out.Close(); e != nil {
err = e
}
}()
_, err = io.Copy(out, in)
if err != nil {
return
}
err = out.Sync()
if err != nil {
return
}
stat, err := os.Stat(src)
if err != nil {
return
}
err = os.Chmod(dst, stat.Mode())
if err != nil {
return
}
return
}
func (b *LocalFileBackend) TestConnection() error {
f := bytes.NewReader([]byte("testingwrite"))
if _, err := writeFileLocally(f, filepath.Join(b.directory, TestFilePath)); err != nil {
return errors.Wrap(err, "unable to write to the local filesystem storage")
}
os.Remove(filepath.Join(b.directory, TestFilePath))
mlog.Debug("Able to write files to local storage.")
return nil
}
func (b *LocalFileBackend) Reader(path string) (ReadCloseSeeker, error) {
f, err := os.Open(filepath.Join(b.directory, path))
if err != nil {
return nil, errors.Wrapf(err, "unable to open file %s", path)
}
return f, nil
}
func (b *LocalFileBackend) ReadFile(path string) ([]byte, error) {
f, err := ioutil.ReadFile(filepath.Join(b.directory, path))
if err != nil {
return nil, errors.Wrapf(err, "unable to read file %s", path)
}
return f, nil
}
func (b *LocalFileBackend) FileExists(path string) (bool, error) {
_, err := os.Stat(filepath.Join(b.directory, path))
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, errors.Wrapf(err, "unable to know if file %s exists", path)
}
return true, nil
}
func (b *LocalFileBackend) FileSize(path string) (int64, error) {
info, err := os.Stat(filepath.Join(b.directory, path))
if err != nil {
return 0, errors.Wrapf(err, "unable to get file size for %s", path)
}
return info.Size(), nil
}
func (b *LocalFileBackend) FileModTime(path string) (time.Time, error) {
info, err := os.Stat(filepath.Join(b.directory, path))
if err != nil {
return time.Time{}, errors.Wrapf(err, "unable to get modification time for file %s", path)
}
return info.ModTime(), nil
}
func (b *LocalFileBackend) CopyFile(oldPath, newPath string) error {
if err := copyFile(filepath.Join(b.directory, oldPath), filepath.Join(b.directory, newPath)); err != nil {
return errors.Wrapf(err, "unable to copy file from %s to %s", oldPath, newPath)
}
return nil
}
func (b *LocalFileBackend) MoveFile(oldPath, newPath string) error {
if err := os.MkdirAll(filepath.Dir(filepath.Join(b.directory, newPath)), 0750); err != nil {
return errors.Wrapf(err, "unable to create the new destination directory %s", filepath.Dir(newPath))
}
if err := os.Rename(filepath.Join(b.directory, oldPath), filepath.Join(b.directory, newPath)); err != nil {
return errors.Wrapf(err, "unable to move the file to %s to the destination directory", newPath)
}
return nil
}
func (b *LocalFileBackend) WriteFile(fr io.Reader, path string) (int64, error) {
return writeFileLocally(fr, filepath.Join(b.directory, path))
}
func writeFileLocally(fr io.Reader, path string) (int64, error) {
if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
directory, _ := filepath.Abs(filepath.Dir(path))
return 0, errors.Wrapf(err, "unable to create the directory %s for the file %s", directory, path)
}
fw, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return 0, errors.Wrapf(err, "unable to open the file %s to write the data", path)
}
defer fw.Close()
written, err := io.Copy(fw, fr)
if err != nil {
return written, errors.Wrapf(err, "unable write the data in the file %s", path)
}
return written, nil
}
func (b *LocalFileBackend) AppendFile(fr io.Reader, path string) (int64, error) {
fp := filepath.Join(b.directory, path)
if _, err := os.Stat(fp); err != nil {
return 0, errors.Wrapf(err, "unable to find the file %s to append the data", path)
}
fw, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return 0, errors.Wrapf(err, "unable to open the file %s to append the data", path)
}
defer fw.Close()
written, err := io.Copy(fw, fr)
if err != nil {
return written, errors.Wrapf(err, "unable append the data in the file %s", path)
}
return written, nil
}
func (b *LocalFileBackend) RemoveFile(path string) error {
if err := os.Remove(filepath.Join(b.directory, path)); err != nil {
return errors.Wrapf(err, "unable to remove the file %s", path)
}
return nil
}
func (b *LocalFileBackend) ListDirectory(path string) ([]string, error) {
var paths []string
fileInfos, err := ioutil.ReadDir(filepath.Join(b.directory, path))
if err != nil {
if os.IsNotExist(err) {
return paths, nil
}
return nil, errors.Wrapf(err, "unable to list the directory %s", path)
}
for _, fileInfo := range fileInfos {
paths = append(paths, filepath.Join(path, fileInfo.Name()))
}
return paths, nil
}
func (b *LocalFileBackend) RemoveDirectory(path string) error {
if err := os.RemoveAll(filepath.Join(b.directory, path)); err != nil {
return errors.Wrapf(err, "unable to remove the directory %s", path)
}
return nil
}

View File

@ -0,0 +1,56 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package filestore
import (
"context"
"net/http"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// customTransport is used to point the request to a different server.
// This is helpful in situations where a different service is handling AWS S3 requests
// from multiple Mattermost applications, and the Mattermost service itself does not
// have any S3 credentials.
type customTransport struct {
base http.RoundTripper
host string
scheme string
client http.Client
}
// RoundTrip implements the http.Roundtripper interface.
func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Rountrippers should not modify the original request.
newReq := req.Clone(context.Background())
*newReq.URL = *req.URL
req.URL.Scheme = t.scheme
req.URL.Host = t.host
return t.client.Do(req)
}
// customProvider is a dummy credentials provider for the minio client to work
// without actually providing credentials. This is needed with a custom transport
// in cases where the minio client does not actually have credentials with itself,
// rather needs responses from another entity.
//
// It satisfies the credentials.Provider interface.
type customProvider struct {
isSignV2 bool
}
// Retrieve just returns empty credentials.
func (cp customProvider) Retrieve() (credentials.Value, error) {
sign := credentials.SignatureV4
if cp.isSignV2 {
sign = credentials.SignatureV2
}
return credentials.Value{
SignerType: sign,
}, nil
}
// IsExpired always returns false.
func (cp customProvider) IsExpired() bool { return false }

View File

@ -0,0 +1,442 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package filestore
import (
"context"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
s3 "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
// S3FileBackend contains all necessary information to communicate with
// an AWS S3 compatible API backend.
type S3FileBackend struct {
endpoint string
accessKey string
secretKey string
secure bool
signV2 bool
region string
bucket string
pathPrefix string
encrypt bool
trace bool
client *s3.Client
}
type S3FileBackendAuthError struct {
DetailedError string
}
// S3FileBackendNoBucketError is returned when testing a connection and no S3 bucket is found
type S3FileBackendNoBucketError struct{}
const (
// This is not exported by minio. See: https://github.com/minio/minio-go/issues/1339
bucketNotFound = "NoSuchBucket"
)
var (
imageExtensions = map[string]bool{".jpg": true, ".jpeg": true, ".gif": true, ".bmp": true, ".png": true, ".tiff": true, "tif": true}
imageMimeTypes = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff", ".tif": "image/tif"}
)
func isFileExtImage(ext string) bool {
ext = strings.ToLower(ext)
return imageExtensions[ext]
}
func getImageMimeType(ext string) string {
ext = strings.ToLower(ext)
if imageMimeTypes[ext] == "" {
return "image"
}
return imageMimeTypes[ext]
}
func (s *S3FileBackendAuthError) Error() string {
return s.DetailedError
}
func (s *S3FileBackendNoBucketError) Error() string {
return "no such bucket"
}
// NewS3FileBackend returns an instance of an S3FileBackend.
func NewS3FileBackend(settings FileBackendSettings) (*S3FileBackend, error) {
backend := &S3FileBackend{
endpoint: settings.AmazonS3Endpoint,
accessKey: settings.AmazonS3AccessKeyId,
secretKey: settings.AmazonS3SecretAccessKey,
secure: settings.AmazonS3SSL,
signV2: settings.AmazonS3SignV2,
region: settings.AmazonS3Region,
bucket: settings.AmazonS3Bucket,
pathPrefix: settings.AmazonS3PathPrefix,
encrypt: settings.AmazonS3SSE,
trace: settings.AmazonS3Trace,
}
cli, err := backend.s3New()
if err != nil {
return nil, err
}
backend.client = cli
return backend, nil
}
// Similar to s3.New() but allows initialization of signature v2 or signature v4 client.
// If signV2 input is false, function always returns signature v4.
//
// Additionally this function also takes a user defined region, if set
// disables automatic region lookup.
func (b *S3FileBackend) s3New() (*s3.Client, error) {
var creds *credentials.Credentials
isCloud := os.Getenv("MM_CLOUD_FILESTORE_BIFROST") != ""
if isCloud {
creds = credentials.New(customProvider{isSignV2: b.signV2})
} else if b.accessKey == "" && b.secretKey == "" {
creds = credentials.NewIAM("")
} else if b.signV2 {
creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV2)
} else {
creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV4)
}
opts := s3.Options{
Creds: creds,
Secure: b.secure,
Region: b.region,
}
// If this is a cloud installation, we override the default transport.
if isCloud {
tr, err := s3.DefaultTransport(b.secure)
if err != nil {
return nil, err
}
scheme := "http"
if b.secure {
scheme = "https"
}
opts.Transport = &customTransport{
base: tr,
host: b.endpoint,
scheme: scheme,
}
}
s3Clnt, err := s3.New(b.endpoint, &opts)
if err != nil {
return nil, err
}
if b.trace {
s3Clnt.TraceOn(os.Stdout)
}
return s3Clnt, nil
}
func (b *S3FileBackend) TestConnection() error {
exists := true
var err error
// If a path prefix is present, we attempt to test the bucket by listing objects under the path
// and just checking the first response. This is because the BucketExists call is only at a bucket level
// and sometimes the user might only be allowed access to the specified path prefix.
if b.pathPrefix != "" {
obj := <-b.client.ListObjects(context.Background(), b.bucket, s3.ListObjectsOptions{Prefix: b.pathPrefix})
if obj.Err != nil {
typedErr := s3.ToErrorResponse(obj.Err)
if typedErr.Code != bucketNotFound {
return &S3FileBackendAuthError{DetailedError: "unable to list objects in the S3 bucket"}
}
exists = false
}
} else {
exists, err = b.client.BucketExists(context.Background(), b.bucket)
if err != nil {
return &S3FileBackendAuthError{DetailedError: "unable to check if the S3 bucket exists"}
}
}
if !exists {
return &S3FileBackendNoBucketError{}
}
mlog.Debug("Connection to S3 or minio is good. Bucket exists.")
return nil
}
func (b *S3FileBackend) MakeBucket() error {
err := b.client.MakeBucket(context.Background(), b.bucket, s3.MakeBucketOptions{Region: b.region})
if err != nil {
return errors.Wrap(err, "unable to create the s3 bucket")
}
return nil
}
// Caller must close the first return value
func (b *S3FileBackend) Reader(path string) (ReadCloseSeeker, error) {
path = filepath.Join(b.pathPrefix, path)
minioObject, err := b.client.GetObject(context.Background(), b.bucket, path, s3.GetObjectOptions{})
if err != nil {
return nil, errors.Wrapf(err, "unable to open file %s", path)
}
return minioObject, nil
}
func (b *S3FileBackend) ReadFile(path string) ([]byte, error) {
path = filepath.Join(b.pathPrefix, path)
minioObject, err := b.client.GetObject(context.Background(), b.bucket, path, s3.GetObjectOptions{})
if err != nil {
return nil, errors.Wrapf(err, "unable to open file %s", path)
}
defer minioObject.Close()
f, err := ioutil.ReadAll(minioObject)
if err != nil {
return nil, errors.Wrapf(err, "unable to read file %s", path)
}
return f, nil
}
func (b *S3FileBackend) FileExists(path string) (bool, error) {
path = filepath.Join(b.pathPrefix, path)
_, err := b.client.StatObject(context.Background(), b.bucket, path, s3.StatObjectOptions{})
if err == nil {
return true, nil
}
var s3Err s3.ErrorResponse
if errors.As(err, &s3Err); s3Err.Code == "NoSuchKey" {
return false, nil
}
return false, errors.Wrapf(err, "unable to know if file %s exists", path)
}
func (b *S3FileBackend) FileSize(path string) (int64, error) {
path = filepath.Join(b.pathPrefix, path)
info, err := b.client.StatObject(context.Background(), b.bucket, path, s3.StatObjectOptions{})
if err != nil {
return 0, errors.Wrapf(err, "unable to get file size for %s", path)
}
return info.Size, nil
}
func (b *S3FileBackend) FileModTime(path string) (time.Time, error) {
path = filepath.Join(b.pathPrefix, path)
info, err := b.client.StatObject(context.Background(), b.bucket, path, s3.StatObjectOptions{})
if err != nil {
return time.Time{}, errors.Wrapf(err, "unable to get modification time for file %s", path)
}
return info.LastModified, nil
}
func (b *S3FileBackend) CopyFile(oldPath, newPath string) error {
oldPath = filepath.Join(b.pathPrefix, oldPath)
newPath = filepath.Join(b.pathPrefix, newPath)
srcOpts := s3.CopySrcOptions{
Bucket: b.bucket,
Object: oldPath,
Encryption: encrypt.NewSSE(),
}
dstOpts := s3.CopyDestOptions{
Bucket: b.bucket,
Object: newPath,
Encryption: encrypt.NewSSE(),
}
if _, err := b.client.CopyObject(context.Background(), dstOpts, srcOpts); err != nil {
return errors.Wrapf(err, "unable to copy file from %s to %s", oldPath, newPath)
}
return nil
}
func (b *S3FileBackend) MoveFile(oldPath, newPath string) error {
oldPath = filepath.Join(b.pathPrefix, oldPath)
newPath = filepath.Join(b.pathPrefix, newPath)
srcOpts := s3.CopySrcOptions{
Bucket: b.bucket,
Object: oldPath,
Encryption: encrypt.NewSSE(),
}
dstOpts := s3.CopyDestOptions{
Bucket: b.bucket,
Object: newPath,
Encryption: encrypt.NewSSE(),
}
if _, err := b.client.CopyObject(context.Background(), dstOpts, srcOpts); err != nil {
return errors.Wrapf(err, "unable to copy the file to %s to the new destionation", newPath)
}
if err := b.client.RemoveObject(context.Background(), b.bucket, oldPath, s3.RemoveObjectOptions{}); err != nil {
return errors.Wrapf(err, "unable to remove the file old file %s", oldPath)
}
return nil
}
func (b *S3FileBackend) WriteFile(fr io.Reader, path string) (int64, error) {
var contentType string
path = filepath.Join(b.pathPrefix, path)
if ext := filepath.Ext(path); isFileExtImage(ext) {
contentType = getImageMimeType(ext)
} else {
contentType = "binary/octet-stream"
}
options := s3PutOptions(b.encrypt, contentType)
info, err := b.client.PutObject(context.Background(), b.bucket, path, fr, -1, options)
if err != nil {
return info.Size, errors.Wrapf(err, "unable write the data in the file %s", path)
}
return info.Size, nil
}
func (b *S3FileBackend) AppendFile(fr io.Reader, path string) (int64, error) {
fp := filepath.Join(b.pathPrefix, path)
if _, err := b.client.StatObject(context.Background(), b.bucket, fp, s3.StatObjectOptions{}); err != nil {
return 0, errors.Wrapf(err, "unable to find the file %s to append the data", path)
}
var contentType string
if ext := filepath.Ext(fp); isFileExtImage(ext) {
contentType = getImageMimeType(ext)
} else {
contentType = "binary/octet-stream"
}
options := s3PutOptions(b.encrypt, contentType)
sse := options.ServerSideEncryption
partName := fp + ".part"
info, err := b.client.PutObject(context.Background(), b.bucket, partName, fr, -1, options)
defer b.client.RemoveObject(context.Background(), b.bucket, partName, s3.RemoveObjectOptions{})
if info.Size > 0 {
src1Opts := s3.CopySrcOptions{
Bucket: b.bucket,
Object: fp,
}
src2Opts := s3.CopySrcOptions{
Bucket: b.bucket,
Object: partName,
}
dstOpts := s3.CopyDestOptions{
Bucket: b.bucket,
Object: fp,
Encryption: sse,
}
_, err = b.client.ComposeObject(context.Background(), dstOpts, src1Opts, src2Opts)
if err != nil {
return 0, errors.Wrapf(err, "unable append the data in the file %s", path)
}
return info.Size, nil
}
return 0, errors.Wrapf(err, "unable append the data in the file %s", path)
}
func (b *S3FileBackend) RemoveFile(path string) error {
path = filepath.Join(b.pathPrefix, path)
if err := b.client.RemoveObject(context.Background(), b.bucket, path, s3.RemoveObjectOptions{}); err != nil {
return errors.Wrapf(err, "unable to remove the file %s", path)
}
return nil
}
func getPathsFromObjectInfos(in <-chan s3.ObjectInfo) <-chan s3.ObjectInfo {
out := make(chan s3.ObjectInfo, 1)
go func() {
defer close(out)
for {
info, done := <-in
if !done {
break
}
out <- info
}
}()
return out
}
func (b *S3FileBackend) ListDirectory(path string) ([]string, error) {
path = filepath.Join(b.pathPrefix, path)
if !strings.HasSuffix(path, "/") && path != "" {
// s3Clnt returns only the path itself when "/" is not present
// appending "/" to make it consistent across all filestores
path = path + "/"
}
opts := s3.ListObjectsOptions{
Prefix: path,
}
var paths []string
for object := range b.client.ListObjects(context.Background(), b.bucket, opts) {
if object.Err != nil {
return nil, errors.Wrapf(object.Err, "unable to list the directory %s", path)
}
// We strip the path prefix that gets applied,
// so that it remains transparent to the application.
object.Key = strings.TrimPrefix(object.Key, b.pathPrefix)
trimmed := strings.Trim(object.Key, "/")
if trimmed != "" {
paths = append(paths, trimmed)
}
}
return paths, nil
}
func (b *S3FileBackend) RemoveDirectory(path string) error {
opts := s3.ListObjectsOptions{
Prefix: filepath.Join(b.pathPrefix, path),
Recursive: true,
}
list := b.client.ListObjects(context.Background(), b.bucket, opts)
objectsCh := b.client.RemoveObjects(context.Background(), b.bucket, getPathsFromObjectInfos(list), s3.RemoveObjectsOptions{})
for err := range objectsCh {
if err.Err != nil {
return errors.Wrapf(err.Err, "unable to remove the directory %s", path)
}
}
return nil
}
func s3PutOptions(encrypted bool, contentType string) s3.PutObjectOptions {
options := s3.PutObjectOptions{}
if encrypted {
options.ServerSideEncryption = encrypt.NewSSE()
}
options.ContentType = contentType
// We set the part size to the minimum allowed value of 5MBs
// to avoid an excessive allocation in minio.PutObject implementation.
options.PartSize = 1024 * 1024 * 5
return options
}

View File

@ -0,0 +1,185 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package i18n
import (
"fmt"
"html/template"
"io/ioutil"
"net/http"
"path/filepath"
"reflect"
"strings"
"github.com/mattermost/go-i18n/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
)
const defaultLocale = "en"
// TranslateFunc is the type of the translate functions
type TranslateFunc func(translationID string, args ...interface{}) string
// T is the translate function using the default server language as fallback language
var T TranslateFunc
// TDefault is the translate function using english as fallback language
var TDefault TranslateFunc
var locales map[string]string = make(map[string]string)
var defaultServerLocale string
var defaultClientLocale string
// TranslationsPreInit loads translations from filesystem if they are not
// loaded already and assigns english while loading server config
func TranslationsPreInit(translationsDir string) error {
if T != nil {
return nil
}
// Set T even if we fail to load the translations. Lots of shutdown handling code will
// segfault trying to handle the error, and the untranslated IDs are strictly better.
T = tfuncWithFallback(defaultLocale)
TDefault = tfuncWithFallback(defaultLocale)
return initTranslationsWithDir(translationsDir)
}
// InitTranslations set the defaults configured in the server and initialize
// the T function using the server default as fallback language
func InitTranslations(serverLocale, clientLocale string) error {
defaultServerLocale = serverLocale
defaultClientLocale = clientLocale
var err error
T, err = getTranslationsBySystemLocale()
return err
}
func initTranslationsWithDir(dir string) error {
files, _ := ioutil.ReadDir(dir)
for _, f := range files {
if filepath.Ext(f.Name()) == ".json" {
filename := f.Name()
locales[strings.Split(filename, ".")[0]] = filepath.Join(dir, filename)
if err := i18n.LoadTranslationFile(filepath.Join(dir, filename)); err != nil {
return err
}
}
}
return nil
}
func getTranslationsBySystemLocale() (TranslateFunc, error) {
locale := defaultServerLocale
if _, ok := locales[locale]; !ok {
mlog.Warn("Failed to load system translations for", mlog.String("locale", locale), mlog.String("attempting to fall back to default locale", defaultLocale))
locale = defaultLocale
}
if locales[locale] == "" {
return nil, fmt.Errorf("failed to load system translations for '%v'", defaultLocale)
}
translations := tfuncWithFallback(locale)
if translations == nil {
return nil, fmt.Errorf("failed to load system translations")
}
mlog.Info("Loaded system translations", mlog.String("for locale", locale), mlog.String("from locale", locales[locale]))
return translations, nil
}
// GetUserTranslations get the translation function for an specific locale
func GetUserTranslations(locale string) TranslateFunc {
if _, ok := locales[locale]; !ok {
locale = defaultLocale
}
translations := tfuncWithFallback(locale)
return translations
}
// GetTranslationsAndLocaleFromRequest return the translation function and the
// locale based on a request headers
func GetTranslationsAndLocaleFromRequest(r *http.Request) (TranslateFunc, string) {
// This is for checking against locales like pt_BR or zn_CN
headerLocaleFull := strings.Split(r.Header.Get("Accept-Language"), ",")[0]
// This is for checking against locales like en, es
headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0]
defaultLocale := defaultClientLocale
if locales[headerLocaleFull] != "" {
translations := tfuncWithFallback(headerLocaleFull)
return translations, headerLocaleFull
} else if locales[headerLocale] != "" {
translations := tfuncWithFallback(headerLocale)
return translations, headerLocale
} else if locales[defaultLocale] != "" {
translations := tfuncWithFallback(defaultLocale)
return translations, headerLocale
}
translations := tfuncWithFallback(defaultLocale)
return translations, defaultLocale
}
// GetSupportedLocales return a map of locale code and the file path with the
// translations
func GetSupportedLocales() map[string]string {
return locales
}
func tfuncWithFallback(pref string) TranslateFunc {
t, _ := i18n.Tfunc(pref)
return func(translationID string, args ...interface{}) string {
if translated := t(translationID, args...); translated != translationID {
return translated
}
t, _ := i18n.Tfunc(defaultLocale)
return t(translationID, args...)
}
}
// TranslateAsHTML translates the translationID provided and return a
// template.HTML object
func TranslateAsHTML(t TranslateFunc, translationID string, args map[string]interface{}) template.HTML {
message := t(translationID, escapeForHTML(args))
message = strings.Replace(message, "[[", "<strong>", -1)
message = strings.Replace(message, "]]", "</strong>", -1)
return template.HTML(message)
}
func escapeForHTML(arg interface{}) interface{} {
switch typedArg := arg.(type) {
case string:
return template.HTMLEscapeString(typedArg)
case *string:
return template.HTMLEscapeString(*typedArg)
case map[string]interface{}:
safeArg := make(map[string]interface{}, len(typedArg))
for key, value := range typedArg {
safeArg[key] = escapeForHTML(value)
}
return safeArg
default:
mlog.Warn(
"Unable to escape value for HTML template",
mlog.Any("html_template", arg),
mlog.String("template_type", reflect.ValueOf(arg).Type().String()),
)
return ""
}
}
// IdentityTfunc returns a translation function that don't translate, only
// returns the same id
func IdentityTfunc() TranslateFunc {
return func(translationID string, args ...interface{}) string {
return translationID
}
}

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