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

Use mattermost v5 module (#1192)

This commit is contained in:
Wim
2020-08-10 00:29:54 +02:00
committed by GitHub
parent dfdffa0027
commit 4e50fd8649
608 changed files with 85280 additions and 25773 deletions

View File

@ -0,0 +1,96 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
const (
ACCESS_TOKEN_GRANT_TYPE = "authorization_code"
ACCESS_TOKEN_TYPE = "bearer"
REFRESH_TOKEN_GRANT_TYPE = "refresh_token"
)
type AccessData struct {
ClientId string `json:"client_id"`
UserId string `json:"user_id"`
Token string `json:"token"`
RefreshToken string `json:"refresh_token"`
RedirectUri string `json:"redirect_uri"`
ExpiresAt int64 `json:"expires_at"`
Scope string `json:"scope"`
}
type AccessResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int32 `json:"expires_in"`
Scope string `json:"scope"`
RefreshToken string `json:"refresh_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 {
return NewAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.UserId) == 0 || len(ad.UserId) > 26 {
return NewAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.Token) != 26 {
return NewAppError("AccessData.IsValid", "model.access.is_valid.access_token.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.RefreshToken) > 26 {
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) {
return NewAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (ad *AccessData) IsExpired() bool {
if ad.ExpiresAt <= 0 {
return false
}
if GetMillis() > ad.ExpiresAt {
return true
}
return false
}
func (ad *AccessData) ToJson() string {
b, _ := json.Marshal(ad)
return string(b)
}
func AccessDataFromJson(data io.Reader) *AccessData {
var ad *AccessData
json.NewDecoder(data).Decode(&ad)
return ad
}
func (ar *AccessResponse) ToJson() string {
b, _ := json.Marshal(ar)
return string(b)
}
func AccessResponseFromJson(data io.Reader) *AccessResponse {
var ar *AccessResponse
json.NewDecoder(data).Decode(&ar)
return ar
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type AnalyticsRow struct {
Name string `json:"name"`
Value float64 `json:"value"`
}
type AnalyticsRows []*AnalyticsRow
func (me *AnalyticsRow) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow {
var me *AnalyticsRow
json.NewDecoder(data).Decode(&me)
return me
}
func (me AnalyticsRows) ToJson() string {
if b, err := json.Marshal(me); err != nil {
return "[]"
} else {
return string(b)
}
}
func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows {
var me AnalyticsRows
json.NewDecoder(data).Decode(&me)
return me
}

View File

@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"regexp"
"strings"
)
var atMentionRegexp = regexp.MustCompile(`\B@[[:alnum:]][[:alnum:]\.\-_]*`)
const usernameSpecialChars = ".-_"
// PossibleAtMentions returns all substrings in message that look like valid @
// mentions.
func PossibleAtMentions(message string) []string {
var names []string
if !strings.Contains(message, "@") {
return names
}
alreadyMentioned := make(map[string]bool)
for _, match := range atMentionRegexp.FindAllString(message, -1) {
name := NormalizeUsername(match[1:])
if !alreadyMentioned[name] && IsValidUsername(name) {
names = append(names, name)
alreadyMentioned[name] = true
}
}
return names
}
// TrimUsernameSpecialChar tries to remove the last character from word if it
// is a special character for usernames (dot, dash or underscore). If not, it
// returns the same string.
func TrimUsernameSpecialChar(word string) (string, bool) {
len := len(word)
if len > 0 && strings.LastIndexAny(word, usernameSpecialChars) == (len-1) {
return word[:len-1], true
}
return word, false
}

View File

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type Audit struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UserId string `json:"user_id"`
Action string `json:"action"`
ExtraInfo string `json:"extra_info"`
IpAddress string `json:"ip_address"`
SessionId string `json:"session_id"`
}
func (o *Audit) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func AuditFromJson(data io.Reader) *Audit {
var o *Audit
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,667 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
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) {
if val == nil {
return nil, false
}
switch v := val.(type) {
case *Channel:
return newAuditChannel(v), true
case *Team:
return newAuditTeam(v), true
case *User:
return newAuditUser(v), true
case *Command:
return newAuditCommand(v), true
case *CommandArgs:
return newAuditCommandArgs(v), true
case *Bot:
return newAuditBot(v), true
case *ChannelModerationPatch:
return newAuditChannelModerationPatch(v), true
case *Emoji:
return newAuditEmoji(v), true
case *FileInfo:
return newAuditFileInfo(v), true
case *Group:
return newAuditGroup(v), true
case *Job:
return newAuditJob(v), true
case *OAuthApp:
return newAuditOAuthApp(v), true
case *Post:
return newAuditPost(v), true
case *Role:
return newAuditRole(v), true
case *Scheme:
return newAuditScheme(v), true
case *SchemeRoles:
return newAuditSchemeRoles(v), true
case *Session:
return newAuditSession(v), true
case *IncomingWebhook:
return newAuditIncomingWebhook(v), true
case *OutgoingWebhook:
return newAuditOutgoingWebhook(v), true
}
return val, false
}
type auditChannel struct {
ID string
Name string
Type string
}
// newAuditChannel creates a simplified representation of Channel for output to audit log.
func newAuditChannel(c *Channel) auditChannel {
var channel auditChannel
if c != nil {
channel.ID = c.Id
channel.Name = c.Name
channel.Type = c.Type
}
return channel
}
func (c auditChannel) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", c.ID)
enc.StringKey("name", c.Name)
enc.StringKey("type", c.Type)
}
func (c auditChannel) IsNil() bool {
return false
}
type auditTeam struct {
ID string
Name string
Type string
}
// newAuditTeam creates a simplified representation of Team for output to audit log.
func newAuditTeam(t *Team) auditTeam {
var team auditTeam
if t != nil {
team.ID = t.Id
team.Name = t.Name
team.Type = t.Type
}
return team
}
func (t auditTeam) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", t.ID)
enc.StringKey("name", t.Name)
enc.StringKey("type", t.Type)
}
func (t auditTeam) IsNil() bool {
return false
}
type auditUser struct {
ID string
Name string
Roles string
}
// newAuditUser creates a simplified representation of User for output to audit log.
func newAuditUser(u *User) auditUser {
var user auditUser
if u != nil {
user.ID = u.Id
user.Name = u.Username
user.Roles = u.Roles
}
return user
}
func (u auditUser) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", u.ID)
enc.StringKey("name", u.Name)
enc.StringKey("roles", u.Roles)
}
func (u auditUser) IsNil() bool {
return false
}
type auditCommand struct {
ID string
CreatorID string
TeamID string
Trigger string
Method string
Username string
IconURL string
AutoComplete bool
AutoCompleteDesc string
AutoCompleteHint string
DisplayName string
Description string
URL string
}
// newAuditCommand creates a simplified representation of Command for output to audit log.
func newAuditCommand(c *Command) auditCommand {
var cmd auditCommand
if c != nil {
cmd.ID = c.Id
cmd.CreatorID = c.CreatorId
cmd.TeamID = c.TeamId
cmd.Trigger = c.Trigger
cmd.Method = c.Method
cmd.Username = c.Username
cmd.IconURL = c.IconURL
cmd.AutoComplete = c.AutoComplete
cmd.AutoCompleteDesc = c.AutoCompleteDesc
cmd.AutoCompleteHint = c.AutoCompleteHint
cmd.DisplayName = c.DisplayName
cmd.Description = c.Description
cmd.URL = c.URL
}
return cmd
}
func (cmd auditCommand) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", cmd.ID)
enc.StringKey("creator_id", cmd.CreatorID)
enc.StringKey("team_id", cmd.TeamID)
enc.StringKey("trigger", cmd.Trigger)
enc.StringKey("method", cmd.Method)
enc.StringKey("username", cmd.Username)
enc.StringKey("icon_url", cmd.IconURL)
enc.BoolKey("auto_complete", cmd.AutoComplete)
enc.StringKey("auto_complete_desc", cmd.AutoCompleteDesc)
enc.StringKey("auto_complete_hint", cmd.AutoCompleteHint)
enc.StringKey("display", cmd.DisplayName)
enc.StringKey("desc", cmd.Description)
enc.StringKey("url", cmd.URL)
}
func (cmd auditCommand) IsNil() bool {
return false
}
type auditCommandArgs struct {
ChannelID string
TeamID string
TriggerID string
Command string
}
// newAuditCommandArgs creates a simplified representation of CommandArgs for output to audit log.
func newAuditCommandArgs(ca *CommandArgs) auditCommandArgs {
var cmdargs auditCommandArgs
if ca != nil {
cmdargs.ChannelID = ca.ChannelId
cmdargs.TeamID = ca.TeamId
cmdargs.TriggerID = ca.TriggerId
cmdargs.Command = ca.Command
}
return cmdargs
}
func (ca auditCommandArgs) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("channel_id", ca.ChannelID)
enc.StringKey("team_id", ca.TriggerID)
enc.StringKey("trigger_id", ca.TeamID)
enc.StringKey("command", ca.Command)
}
func (ca auditCommandArgs) IsNil() bool {
return false
}
type auditBot struct {
UserID string
Username string
Displayname string
}
// newAuditBot creates a simplified representation of Bot for output to audit log.
func newAuditBot(b *Bot) auditBot {
var bot auditBot
if b != nil {
bot.UserID = b.UserId
bot.Username = b.Username
bot.Displayname = b.DisplayName
}
return bot
}
func (b auditBot) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("user_id", b.UserID)
enc.StringKey("username", b.Username)
enc.StringKey("display", b.Displayname)
}
func (b auditBot) IsNil() bool {
return false
}
type auditChannelModerationPatch struct {
Name string
RoleGuests bool
RoleMembers bool
}
// newAuditChannelModerationPatch creates a simplified representation of ChannelModerationPatch for output to audit log.
func newAuditChannelModerationPatch(p *ChannelModerationPatch) auditChannelModerationPatch {
var patch auditChannelModerationPatch
if p != nil {
if p.Name != nil {
patch.Name = *p.Name
}
if p.Roles.Guests != nil {
patch.RoleGuests = *p.Roles.Guests
}
if p.Roles.Members != nil {
patch.RoleMembers = *p.Roles.Members
}
}
return patch
}
func (p auditChannelModerationPatch) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("name", p.Name)
enc.BoolKey("role_guests", p.RoleGuests)
enc.BoolKey("role_members", p.RoleMembers)
}
func (p auditChannelModerationPatch) IsNil() bool {
return false
}
type auditEmoji struct {
ID string
Name string
}
// newAuditEmoji creates a simplified representation of Emoji for output to audit log.
func newAuditEmoji(e *Emoji) auditEmoji {
var emoji auditEmoji
if e != nil {
emoji.ID = e.Id
emoji.Name = e.Name
}
return emoji
}
func (e auditEmoji) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", e.ID)
enc.StringKey("name", e.Name)
}
func (e auditEmoji) IsNil() bool {
return false
}
type auditFileInfo struct {
ID string
PostID string
Path string
Name string
Extension string
Size int64
}
// newAuditFileInfo creates a simplified representation of FileInfo for output to audit log.
func newAuditFileInfo(f *FileInfo) auditFileInfo {
var fi auditFileInfo
if f != nil {
fi.ID = f.Id
fi.PostID = f.PostId
fi.Path = f.Path
fi.Name = f.Name
fi.Extension = f.Extension
fi.Size = f.Size
}
return fi
}
func (fi auditFileInfo) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", fi.ID)
enc.StringKey("post_id", fi.PostID)
enc.StringKey("path", fi.Path)
enc.StringKey("name", fi.Name)
enc.StringKey("ext", fi.Extension)
enc.Int64Key("size", fi.Size)
}
func (fi auditFileInfo) IsNil() bool {
return false
}
type auditGroup struct {
ID string
Name string
DisplayName string
Description string
}
// newAuditGroup creates a simplified representation of Group for output to audit log.
func newAuditGroup(g *Group) auditGroup {
var group auditGroup
if g != nil {
group.ID = g.Id
if g.Name == nil {
group.Name = ""
} else {
group.Name = *g.Name
}
group.DisplayName = g.DisplayName
group.Description = g.Description
}
return group
}
func (g auditGroup) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", g.ID)
enc.StringKey("name", g.Name)
enc.StringKey("display", g.DisplayName)
enc.StringKey("desc", g.Description)
}
func (g auditGroup) IsNil() bool {
return false
}
type auditJob struct {
ID string
Type string
Priority int64
StartAt int64
}
// newAuditJob creates a simplified representation of Job for output to audit log.
func newAuditJob(j *Job) auditJob {
var job auditJob
if j != nil {
job.ID = j.Id
job.Type = j.Type
job.Priority = j.Priority
job.StartAt = j.StartAt
}
return job
}
func (j auditJob) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", j.ID)
enc.StringKey("type", j.Type)
enc.Int64Key("priority", j.Priority)
enc.Int64Key("start_at", j.StartAt)
}
func (j auditJob) IsNil() bool {
return false
}
type auditOAuthApp struct {
ID string
CreatorID string
Name string
Description string
IsTrusted bool
}
// newAuditOAuthApp creates a simplified representation of OAuthApp for output to audit log.
func newAuditOAuthApp(o *OAuthApp) auditOAuthApp {
var oauth auditOAuthApp
if o != nil {
oauth.ID = o.Id
oauth.CreatorID = o.CreatorId
oauth.Name = o.Name
oauth.Description = o.Description
oauth.IsTrusted = o.IsTrusted
}
return oauth
}
func (o auditOAuthApp) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", o.ID)
enc.StringKey("creator_id", o.CreatorID)
enc.StringKey("name", o.Name)
enc.StringKey("desc", o.Description)
enc.BoolKey("trusted", o.IsTrusted)
}
func (o auditOAuthApp) IsNil() bool {
return false
}
type auditPost struct {
ID string
ChannelID string
Type string
IsPinned bool
}
// newAuditPost creates a simplified representation of Post for output to audit log.
func newAuditPost(p *Post) auditPost {
var post auditPost
if p != nil {
post.ID = p.Id
post.ChannelID = p.ChannelId
post.Type = p.Type
post.IsPinned = p.IsPinned
}
return post
}
func (p auditPost) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", p.ID)
enc.StringKey("channel_id", p.ChannelID)
enc.StringKey("type", p.Type)
enc.BoolKey("pinned", p.IsPinned)
}
func (p auditPost) IsNil() bool {
return false
}
type auditRole struct {
ID string
Name string
DisplayName string
Permissions []string
SchemeManaged bool
BuiltIn bool
}
// newAuditRole creates a simplified representation of Role for output to audit log.
func newAuditRole(r *Role) auditRole {
var role auditRole
if r != nil {
role.ID = r.Id
role.Name = r.Name
role.DisplayName = r.DisplayName
role.Permissions = append(role.Permissions, r.Permissions...)
role.SchemeManaged = r.SchemeManaged
role.BuiltIn = r.BuiltIn
}
return role
}
func (r auditRole) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", r.ID)
enc.StringKey("name", r.Name)
enc.StringKey("display", r.DisplayName)
enc.SliceStringKey("perms", r.Permissions)
enc.BoolKey("schemeManaged", r.SchemeManaged)
enc.BoolKey("builtin", r.BuiltIn)
}
func (r auditRole) IsNil() bool {
return false
}
type auditScheme struct {
ID string
Name string
DisplayName string
Scope string
}
// newAuditScheme creates a simplified representation of Scheme for output to audit log.
func newAuditScheme(s *Scheme) auditScheme {
var scheme auditScheme
if s != nil {
scheme.ID = s.Id
scheme.Name = s.Name
scheme.DisplayName = s.DisplayName
scheme.Scope = s.Scope
}
return scheme
}
func (s auditScheme) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", s.ID)
enc.StringKey("name", s.Name)
enc.StringKey("display", s.DisplayName)
enc.StringKey("scope", s.Scope)
}
func (s auditScheme) IsNil() bool {
return false
}
type auditSchemeRoles struct {
SchemeAdmin bool
SchemeUser bool
SchemeGuest bool
}
// newAuditSchemeRoles creates a simplified representation of SchemeRoles for output to audit log.
func newAuditSchemeRoles(s *SchemeRoles) auditSchemeRoles {
var roles auditSchemeRoles
if s != nil {
roles.SchemeAdmin = s.SchemeAdmin
roles.SchemeUser = s.SchemeUser
roles.SchemeGuest = s.SchemeGuest
}
return roles
}
func (s auditSchemeRoles) MarshalJSONObject(enc *gojay.Encoder) {
enc.BoolKey("admin", s.SchemeAdmin)
enc.BoolKey("user", s.SchemeUser)
enc.BoolKey("guest", s.SchemeGuest)
}
func (s auditSchemeRoles) IsNil() bool {
return false
}
type auditSession struct {
ID string
UserId string
DeviceId string
}
// newAuditSession creates a simplified representation of Session for output to audit log.
func newAuditSession(s *Session) auditSession {
var session auditSession
if s != nil {
session.ID = s.Id
session.UserId = s.UserId
session.DeviceId = s.DeviceId
}
return session
}
func (s auditSession) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", s.ID)
enc.StringKey("user_id", s.UserId)
enc.StringKey("device_id", s.DeviceId)
}
func (s auditSession) IsNil() bool {
return false
}
type auditIncomingWebhook struct {
ID string
ChannelID string
TeamId string
DisplayName string
Description string
}
// newAuditIncomingWebhook creates a simplified representation of IncomingWebhook for output to audit log.
func newAuditIncomingWebhook(h *IncomingWebhook) auditIncomingWebhook {
var hook auditIncomingWebhook
if h != nil {
hook.ID = h.Id
hook.ChannelID = h.ChannelId
hook.TeamId = h.TeamId
hook.DisplayName = h.DisplayName
hook.Description = h.Description
}
return hook
}
func (h auditIncomingWebhook) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", h.ID)
enc.StringKey("channel_id", h.ChannelID)
enc.StringKey("team_id", h.TeamId)
enc.StringKey("display", h.DisplayName)
enc.StringKey("desc", h.Description)
}
func (h auditIncomingWebhook) IsNil() bool {
return false
}
type auditOutgoingWebhook struct {
ID string
ChannelID string
TeamID string
TriggerWords StringArray
TriggerWhen int
DisplayName string
Description string
ContentType string
Username string
}
// newAuditOutgoingWebhook creates a simplified representation of OutgoingWebhook for output to audit log.
func newAuditOutgoingWebhook(h *OutgoingWebhook) auditOutgoingWebhook {
var hook auditOutgoingWebhook
if h != nil {
hook.ID = h.Id
hook.ChannelID = h.ChannelId
hook.TeamID = h.TeamId
hook.TriggerWords = h.TriggerWords
hook.TriggerWhen = h.TriggerWhen
hook.DisplayName = h.DisplayName
hook.Description = h.Description
hook.ContentType = h.ContentType
hook.Username = h.Username
}
return hook
}
func (h auditOutgoingWebhook) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("id", h.ID)
enc.StringKey("channel_id", h.ChannelID)
enc.StringKey("team_id", h.TeamID)
enc.SliceStringKey("trigger_words", h.TriggerWords)
enc.IntKey("trigger_when", h.TriggerWhen)
enc.StringKey("display", h.DisplayName)
enc.StringKey("desc", h.Description)
enc.StringKey("content_type", h.ContentType)
enc.StringKey("username", h.Username)
}
func (h auditOutgoingWebhook) IsNil() bool {
return false
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type Audits []Audit
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 ""
}
}
func (o Audits) ToJson() string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func AuditsFromJson(data io.Reader) Audits {
var o Audits
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,142 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
const (
AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes
AUTHCODE_RESPONSE_TYPE = "code"
IMPLICIT_RESPONSE_TYPE = "token"
DEFAULT_SCOPE = "user"
)
type AuthData struct {
ClientId string `json:"client_id"`
UserId string `json:"user_id"`
Code string `json:"code"`
ExpiresIn int32 `json:"expires_in"`
CreateAt int64 `json:"create_at"`
RedirectUri string `json:"redirect_uri"`
State string `json:"state"`
Scope string `json:"scope"`
}
type AuthorizeRequest struct {
ResponseType string `json:"response_type"`
ClientId string `json:"client_id"`
RedirectUri string `json:"redirect_uri"`
Scope string `json:"scope"`
State string `json:"state"`
}
// IsValid validates the AuthData and returns an error if it isn't configured
// correctly.
func (ad *AuthData) IsValid() *AppError {
if !IsValidId(ad.ClientId) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(ad.UserId) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ad.Code) == 0 || len(ad.Code) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.auth_code.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
if ad.ExpiresIn == 0 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.expires.app_error", nil, "", http.StatusBadRequest)
}
if ad.CreateAt <= 0 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
if len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
if len(ad.State) > 1024 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
if len(ad.Scope) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
return nil
}
// IsValid validates the AuthorizeRequest and returns an error if it isn't configured
// correctly.
func (ar *AuthorizeRequest) IsValid() *AppError {
if !IsValidId(ar.ClientId) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.ResponseType) == 0 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.State) > 1024 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.Scope) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
return nil
}
func (ad *AuthData) PreSave() {
if ad.ExpiresIn == 0 {
ad.ExpiresIn = AUTHCODE_EXPIRE_TIME
}
if ad.CreateAt == 0 {
ad.CreateAt = GetMillis()
}
if len(ad.Scope) == 0 {
ad.Scope = DEFAULT_SCOPE
}
}
func (ad *AuthData) ToJson() string {
b, _ := json.Marshal(ad)
return string(b)
}
func AuthDataFromJson(data io.Reader) *AuthData {
var ad *AuthData
json.NewDecoder(data).Decode(&ad)
return ad
}
func (ar *AuthorizeRequest) ToJson() string {
b, _ := json.Marshal(ar)
return string(b)
}
func AuthorizeRequestFromJson(data io.Reader) *AuthorizeRequest {
var ar *AuthorizeRequest
json.NewDecoder(data).Decode(&ar)
return ar
}
func (ad *AuthData) IsExpired() bool {
return GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000)
}

View File

@ -0,0 +1,233 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"unicode/utf8"
)
const (
BOT_DISPLAY_NAME_MAX_RUNES = USER_FIRST_NAME_MAX_RUNES
BOT_DESCRIPTION_MAX_RUNES = 1024
BOT_CREATOR_ID_MAX_RUNES = KEY_VALUE_PLUGIN_ID_MAX_RUNES // UserId or PluginId
)
// Bot is a special type of User meant for programmatic interactions.
// Note that the primary key of a bot is the UserId, and matches the primary key of the
// corresponding user.
type Bot struct {
UserId string `json:"user_id"`
Username string `json:"username"`
DisplayName string `json:"display_name,omitempty"`
Description string `json:"description,omitempty"`
OwnerId string `json:"owner_id"`
LastIconUpdate int64 `json:"last_icon_update,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
}
// BotPatch is a description of what fields to update on an existing bot.
type BotPatch struct {
Username *string `json:"username"`
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
}
// BotGetOptions acts as a filter on bulk bot fetching queries.
type BotGetOptions struct {
OwnerId string
IncludeDeleted bool
OnlyOrphaned bool
Page int
PerPage int
}
// BotList is a list of bots.
type BotList []*Bot
// Trace describes the minimum information required to identify a bot for the purpose of logging.
func (b *Bot) Trace() map[string]interface{} {
return map[string]interface{}{"user_id": b.UserId}
}
// Clone returns a shallow copy of the bot.
func (b *Bot) Clone() *Bot {
copy := *b
return &copy
}
// IsValid validates the bot and returns an error if it isn't configured correctly.
func (b *Bot) IsValid() *AppError {
if !IsValidId(b.UserId) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if !IsValidUsername(b.Username) {
return NewAppError("Bot.IsValid", "model.bot.is_valid.username.app_error", b.Trace(), "", http.StatusBadRequest)
}
if utf8.RuneCountInString(b.DisplayName) > BOT_DISPLAY_NAME_MAX_RUNES {
return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if utf8.RuneCountInString(b.Description) > BOT_DESCRIPTION_MAX_RUNES {
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 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.creator_id.app_error", b.Trace(), "", http.StatusBadRequest)
}
if b.CreateAt == 0 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.create_at.app_error", b.Trace(), "", http.StatusBadRequest)
}
if b.UpdateAt == 0 {
return NewAppError("Bot.IsValid", "model.bot.is_valid.update_at.app_error", b.Trace(), "", http.StatusBadRequest)
}
return nil
}
// PreSave should be run before saving a new bot to the database.
func (b *Bot) PreSave() {
b.CreateAt = GetMillis()
b.UpdateAt = b.CreateAt
b.DeleteAt = 0
}
// PreUpdate should be run before saving an updated bot to the database.
func (b *Bot) PreUpdate() {
b.UpdateAt = GetMillis()
}
// Etag generates an etag for caching.
func (b *Bot) Etag() string {
return Etag(b.UserId, b.UpdateAt)
}
// ToJson serializes the bot to json.
func (b *Bot) ToJson() []byte {
data, _ := json.Marshal(b)
return data
}
// BotFromJson deserializes a bot from json.
func BotFromJson(data io.Reader) *Bot {
var bot *Bot
json.NewDecoder(data).Decode(&bot)
return bot
}
// Patch modifies an existing bot with optional fields from the given patch.
func (b *Bot) Patch(patch *BotPatch) {
if patch.Username != nil {
b.Username = *patch.Username
}
if patch.DisplayName != nil {
b.DisplayName = *patch.DisplayName
}
if patch.Description != nil {
b.Description = *patch.Description
}
}
// ToJson serializes the bot patch to json.
func (b *BotPatch) ToJson() []byte {
data, err := json.Marshal(b)
if err != nil {
return nil
}
return data
}
// BotPatchFromJson deserializes a bot patch from json.
func BotPatchFromJson(data io.Reader) *BotPatch {
decoder := json.NewDecoder(data)
var botPatch BotPatch
err := decoder.Decode(&botPatch)
if err != nil {
return nil
}
return &botPatch
}
// UserFromBot returns a user model describing the bot fields stored in the User store.
func UserFromBot(b *Bot) *User {
return &User{
Id: b.UserId,
Username: b.Username,
Email: NormalizeEmail(fmt.Sprintf("%s@localhost", b.Username)),
FirstName: b.DisplayName,
Roles: SYSTEM_USER_ROLE_ID,
}
}
// BotFromUser returns a bot model given a user model
func BotFromUser(u *User) *Bot {
return &Bot{
OwnerId: u.Id,
UserId: u.Id,
Username: u.Username,
DisplayName: u.GetDisplayName(SHOW_USERNAME),
}
}
// BotListFromJson deserializes a list of bots from json.
func BotListFromJson(data io.Reader) BotList {
var bots BotList
json.NewDecoder(data).Decode(&bots)
return bots
}
// ToJson serializes a list of bots to json.
func (l *BotList) ToJson() []byte {
b, _ := json.Marshal(l)
return b
}
// Etag computes the etag for a list of bots.
func (l *BotList) Etag() string {
id := "0"
var t int64 = 0
var delta int64 = 0
for _, v := range *l {
if v.UpdateAt > t {
t = v.UpdateAt
id = v.UserId
}
}
return Etag(id, t, delta, len(*l))
}
// MakeBotNotFoundError creates the error returned when a bot does not exist, or when the user isn't allowed to query the bot.
// The errors must the same in both cases to avoid leaking that a user is a bot.
func MakeBotNotFoundError(userId string) *AppError {
return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound)
}
func IsBotDMChannel(channel *Channel, botUserID string) bool {
if channel.Type != CHANNEL_DIRECT {
return false
}
if !strings.HasPrefix(channel.Name, botUserID+"__") && !strings.HasSuffix(channel.Name, "__"+botUserID) {
return false
}
return true
}

View File

@ -0,0 +1,288 @@
// 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

@ -0,0 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
func NewBool(b bool) *bool { return &b }
func NewInt(n int) *int { return &n }
func NewInt64(n int64) *int64 { return &n }
func NewString(s string) *string { return &s }

View File

@ -0,0 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import "github.com/mattermost/mattermost-server/v5/mlog"
type BundleInfo struct {
Path string
Manifest *Manifest
ManifestPath string
ManifestError error
}
func (b *BundleInfo) WrapLogger(logger *mlog.Logger) *mlog.Logger {
if b.Manifest != nil {
return logger.With(mlog.String("plugin_id", b.Manifest.Id))
}
return logger.With(mlog.String("plugin_path", b.Path))
}
// Returns bundle info for the given path. The return value is never nil.
func BundleInfoForPath(path string) *BundleInfo {
m, mpath, err := FindManifest(path)
return &BundleInfo{
Path: path,
Manifest: m,
ManifestPath: mpath,
ManifestError: err,
}
}

View File

@ -0,0 +1,364 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"sort"
"strings"
"unicode/utf8"
)
const (
CHANNEL_OPEN = "O"
CHANNEL_PRIVATE = "P"
CHANNEL_DIRECT = "D"
CHANNEL_GROUP = "G"
CHANNEL_GROUP_MAX_USERS = 8
CHANNEL_GROUP_MIN_USERS = 3
DEFAULT_CHANNEL = "town-square"
CHANNEL_DISPLAY_NAME_MAX_RUNES = 64
CHANNEL_NAME_MIN_LENGTH = 2
CHANNEL_NAME_MAX_LENGTH = 64
CHANNEL_HEADER_MAX_RUNES = 1024
CHANNEL_PURPOSE_MAX_RUNES = 250
CHANNEL_CACHE_SIZE = 25000
CHANNEL_SORT_BY_USERNAME = "username"
CHANNEL_SORT_BY_STATUS = "status"
)
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"`
}
type ChannelWithTeamData struct {
Channel
TeamDisplayName string `json:"team_display_name"`
TeamName string `json:"team_name"`
TeamUpdateAt int64 `json:"team_update_at"`
}
type ChannelsWithCount struct {
Channels *ChannelListWithTeamData `json:"channels"`
TotalCount int64 `json:"total_count"`
}
type ChannelPatch struct {
DisplayName *string `json:"display_name"`
Name *string `json:"name"`
Header *string `json:"header"`
Purpose *string `json:"purpose"`
GroupConstrained *bool `json:"group_constrained"`
}
type ChannelForExport struct {
Channel
TeamName string
SchemeName *string
}
type DirectChannelForExport struct {
Channel
Members *[]string
}
type ChannelModeration struct {
Name string `json:"name"`
Roles *ChannelModeratedRoles `json:"roles"`
}
type ChannelModeratedRoles struct {
Guests *ChannelModeratedRole `json:"guests"`
Members *ChannelModeratedRole `json:"members"`
}
type ChannelModeratedRole struct {
Value bool `json:"value"`
Enabled bool `json:"enabled"`
}
type ChannelModerationPatch struct {
Name *string `json:"name"`
Roles *ChannelModeratedRolesPatch `json:"roles"`
}
type ChannelModeratedRolesPatch struct {
Guests *bool `json:"guests"`
Members *bool `json:"members"`
}
// ChannelSearchOpts contains options for searching channels.
//
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
// ExcludeDefaultChannels will exclude the configured default channels (ex 'town-square' and 'off-topic').
// IncludeDeleted will include channel records where DeleteAt != 0.
// ExcludeChannelNames will exclude channels from the results by name.
// Paginate whether to paginate the results.
// Page page requested, if results are paginated.
// PerPage number of results per page, if paginated.
//
type ChannelSearchOpts struct {
NotAssociatedToGroup string
ExcludeDefaultChannels bool
IncludeDeleted bool
ExcludeChannelNames []string
Page *int
PerPage *int
}
type ChannelMemberCountByGroup struct {
GroupId string `db:"-" json:"group_id"`
ChannelMemberCount int64 `db:"-" json:"channel_member_count"`
ChannelMemberTimezonesCount int64 `db:"-" json:"channel_member_timezones_count"`
}
func (o *Channel) DeepCopy() *Channel {
copy := *o
if copy.SchemeId != nil {
copy.SchemeId = NewString(*o.SchemeId)
}
return &copy
}
func (o *Channel) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func (o *ChannelPatch) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func (o *ChannelsWithCount) ToJson() []byte {
b, _ := json.Marshal(o)
return b
}
func ChannelsWithCountFromJson(data io.Reader) *ChannelsWithCount {
var o *ChannelsWithCount
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelFromJson(data io.Reader) *Channel {
var o *Channel
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelPatchFromJson(data io.Reader) *ChannelPatch {
var o *ChannelPatch
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelModerationsFromJson(data io.Reader) []*ChannelModeration {
var o []*ChannelModeration
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelModerationsPatchFromJson(data io.Reader) []*ChannelModerationPatch {
var o []*ChannelModerationPatch
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelMemberCountsByGroupFromJson(data io.Reader) []*ChannelMemberCountByGroup {
var o []*ChannelMemberCountByGroup
json.NewDecoder(data).Decode(&o)
return o
}
func (o *Channel) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
func (o *Channel) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.DisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES {
return NewAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidChannelIdentifier(o.Name) {
return NewAppError("Channel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP) {
return NewAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.Header) > CHANNEL_HEADER_MAX_RUNES {
return NewAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.Purpose) > CHANNEL_PURPOSE_MAX_RUNES {
return NewAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.CreatorId) > 26 {
return NewAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "", http.StatusBadRequest)
}
userIds := strings.Split(o.Name, "__")
if o.Type != CHANNEL_DIRECT && len(userIds) == 2 && IsValidId(userIds[0]) && IsValidId(userIds[1]) {
return NewAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *Channel) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.Name = SanitizeUnicode(o.Name)
o.DisplayName = SanitizeUnicode(o.DisplayName)
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
o.ExtraUpdateAt = 0
}
func (o *Channel) PreUpdate() {
o.UpdateAt = GetMillis()
o.Name = SanitizeUnicode(o.Name)
o.DisplayName = SanitizeUnicode(o.DisplayName)
}
func (o *Channel) IsGroupOrDirect() bool {
return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP
}
func (o *Channel) IsOpen() bool {
return o.Type == CHANNEL_OPEN
}
func (o *Channel) Patch(patch *ChannelPatch) {
if patch.DisplayName != nil {
o.DisplayName = *patch.DisplayName
}
if patch.Name != nil {
o.Name = *patch.Name
}
if patch.Header != nil {
o.Header = *patch.Header
}
if patch.Purpose != nil {
o.Purpose = *patch.Purpose
}
if patch.GroupConstrained != nil {
o.GroupConstrained = patch.GroupConstrained
}
}
func (o *Channel) MakeNonNil() {
if o.Props == nil {
o.Props = make(map[string]interface{})
}
}
func (o *Channel) AddProp(key string, value interface{}) {
o.MakeNonNil()
o.Props[key] = value
}
func (o *Channel) IsGroupConstrained() bool {
return o.GroupConstrained != nil && *o.GroupConstrained
}
func (o *Channel) GetOtherUserIdForDM(userId string) string {
if o.Type != CHANNEL_DIRECT {
return ""
}
userIds := strings.Split(o.Name, "__")
var otherUserId string
if userIds[0] != userIds[1] {
if userIds[0] == userId {
otherUserId = userIds[1]
} else {
otherUserId = userIds[0]
}
}
return otherUserId
}
func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 {
return userId2 + "__" + userId1
} else {
return userId1 + "__" + userId2
}
}
func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string {
usernames := make([]string, len(users))
for index, user := range users {
usernames[index] = user.Username
}
sort.Strings(usernames)
name := strings.Join(usernames, ", ")
if truncate && len(name) > CHANNEL_NAME_MAX_LENGTH {
name = name[:CHANNEL_NAME_MAX_LENGTH]
}
return name
}
func GetGroupNameFromUserIds(userIds []string) string {
sort.Strings(userIds)
h := sha1.New()
for _, id := range userIds {
io.WriteString(h, id)
}
return hex.EncodeToString(h.Sum(nil))
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"crypto/md5"
"encoding/json"
"fmt"
"io"
"sort"
"strconv"
)
type ChannelCounts struct {
Counts map[string]int64 `json:"counts"`
UpdateTimes map[string]int64 `json:"update_times"`
}
func (o *ChannelCounts) Etag() string {
ids := []string{}
for id := range o.Counts {
ids = append(ids, id)
}
sort.Strings(ids)
str := ""
for _, id := range ids {
str += id + strconv.FormatInt(o.Counts[id], 10)
}
md5Counts := fmt.Sprintf("%x", md5.Sum([]byte(str)))
var update int64 = 0
for _, u := range o.UpdateTimes {
if u > update {
update = u
}
}
return Etag(md5Counts, update)
}
func (o *ChannelCounts) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelCountsFromJson(data io.Reader) *ChannelCounts {
var o *ChannelCounts
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ChannelData struct {
Channel *Channel `json:"channel"`
Member *ChannelMember `json:"member"`
}
func (o *ChannelData) Etag() string {
var mt int64 = 0
if o.Member != nil {
mt = o.Member.LastUpdateAt
}
return Etag(o.Channel.Id, o.Channel.UpdateAt, o.Channel.LastPostAt, mt)
}
func (o *ChannelData) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelDataFromJson(data io.Reader) *ChannelData {
var o *ChannelData
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,95 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ChannelList []*Channel
func (o *ChannelList) ToJson() string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func (o *ChannelList) Etag() string {
id := "0"
var t int64 = 0
var delta int64 = 0
for _, v := range *o {
if v.LastPostAt > t {
t = v.LastPostAt
id = v.Id
}
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
}
}
return Etag(id, t, delta, len(*o))
}
func ChannelListFromJson(data io.Reader) *ChannelList {
var o *ChannelList
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelSliceFromJson(data io.Reader) []*Channel {
var o []*Channel
json.NewDecoder(data).Decode(&o)
return o
}
type ChannelListWithTeamData []*ChannelWithTeamData
func (o *ChannelListWithTeamData) ToJson() string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func (o *ChannelListWithTeamData) Etag() string {
id := "0"
var t int64 = 0
var delta int64 = 0
for _, v := range *o {
if v.LastPostAt > t {
t = v.LastPostAt
id = v.Id
}
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
}
if v.TeamUpdateAt > t {
t = v.TeamUpdateAt
id = v.Id
}
}
return Etag(id, t, delta, len(*o))
}
func ChannelListWithTeamDataFromJson(data io.Reader) *ChannelListWithTeamData {
var o *ChannelListWithTeamData
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,194 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"strings"
)
const (
CHANNEL_NOTIFY_DEFAULT = "default"
CHANNEL_NOTIFY_ALL = "all"
CHANNEL_NOTIFY_MENTION = "mention"
CHANNEL_NOTIFY_NONE = "none"
CHANNEL_MARK_UNREAD_ALL = "all"
CHANNEL_MARK_UNREAD_MENTION = "mention"
IGNORE_CHANNEL_MENTIONS_DEFAULT = "default"
IGNORE_CHANNEL_MENTIONS_OFF = "off"
IGNORE_CHANNEL_MENTIONS_ON = "on"
IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP = "ignore_channel_mentions"
)
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:"-"`
}
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:"-"`
}
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"`
}
type ChannelMembers []ChannelMember
type ChannelMemberForExport struct {
ChannelMember
ChannelName string
Username string
}
func (o *ChannelMembers) ToJson() string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func (o *ChannelUnread) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func (o *ChannelUnreadAt) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelMembersFromJson(data io.Reader) *ChannelMembers {
var o *ChannelMembers
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelUnreadFromJson(data io.Reader) *ChannelUnread {
var o *ChannelUnread
json.NewDecoder(data).Decode(&o)
return o
}
func ChannelUnreadAtFromJson(data io.Reader) *ChannelUnreadAt {
var o *ChannelUnreadAt
json.NewDecoder(data).Decode(&o)
return o
}
func (o *ChannelMember) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelMemberFromJson(data io.Reader) *ChannelMember {
var o *ChannelMember
json.NewDecoder(data).Decode(&o)
return o
}
func (o *ChannelMember) IsValid() *AppError {
if !IsValidId(o.ChannelId) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.UserId) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
notifyLevel := o.NotifyProps[DESKTOP_NOTIFY_PROP]
if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error", nil, "notify_level="+notifyLevel, http.StatusBadRequest)
}
markUnreadLevel := o.NotifyProps[MARK_UNREAD_NOTIFY_PROP]
if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error", nil, "mark_unread_level="+markUnreadLevel, http.StatusBadRequest)
}
if pushLevel, ok := o.NotifyProps[PUSH_NOTIFY_PROP]; ok {
if len(pushLevel) > 20 || !IsChannelNotifyLevelValid(pushLevel) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.push_level.app_error", nil, "push_notification_level="+pushLevel, http.StatusBadRequest)
}
}
if sendEmail, ok := o.NotifyProps[EMAIL_NOTIFY_PROP]; ok {
if len(sendEmail) > 20 || !IsSendEmailValid(sendEmail) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.email_value.app_error", nil, "push_notification_level="+sendEmail, http.StatusBadRequest)
}
}
if ignoreChannelMentions, ok := o.NotifyProps[IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; ok {
if len(ignoreChannelMentions) > 40 || !IsIgnoreChannelMentionsValid(ignoreChannelMentions) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.ignore_channel_mentions_value.app_error", nil, "ignore_channel_mentions="+ignoreChannelMentions, http.StatusBadRequest)
}
}
return nil
}
func (o *ChannelMember) PreSave() {
o.LastUpdateAt = GetMillis()
}
func (o *ChannelMember) PreUpdate() {
o.LastUpdateAt = GetMillis()
}
func (o *ChannelMember) GetRoles() []string {
return strings.Fields(o.Roles)
}
func IsChannelNotifyLevelValid(notifyLevel string) bool {
return notifyLevel == CHANNEL_NOTIFY_DEFAULT ||
notifyLevel == CHANNEL_NOTIFY_ALL ||
notifyLevel == CHANNEL_NOTIFY_MENTION ||
notifyLevel == CHANNEL_NOTIFY_NONE
}
func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool {
return markUnreadLevel == CHANNEL_MARK_UNREAD_ALL || markUnreadLevel == CHANNEL_MARK_UNREAD_MENTION
}
func IsSendEmailValid(sendEmail string) bool {
return sendEmail == CHANNEL_NOTIFY_DEFAULT || sendEmail == "true" || sendEmail == "false"
}
func IsIgnoreChannelMentionsValid(ignoreChannelMentions string) bool {
return ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_ON || ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_OFF || ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_DEFAULT
}
func GetDefaultChannelNotifyProps() StringMap {
return StringMap{
DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL,
PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP: IGNORE_CHANNEL_MENTIONS_DEFAULT,
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
type ChannelMemberHistory struct {
ChannelId string
UserId string
JoinTime int64
LeaveTime *int64
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
type ChannelMemberHistoryResult struct {
ChannelId string
UserId string
JoinTime int64
LeaveTime *int64
// these two fields are never set in the database - when we SELECT, we join on Users to get them
UserEmail string `db:"Email"`
Username string
IsBot bool
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"regexp"
"strings"
)
var channelMentionRegexp = regexp.MustCompile(`\B~[a-zA-Z0-9\-_]+`)
func ChannelMentions(message string) []string {
var names []string
if strings.Contains(message, "~") {
alreadyMentioned := make(map[string]bool)
for _, match := range channelMentionRegexp.FindAllString(message, -1) {
name := match[1:]
if !alreadyMentioned[name] {
names = append(names, name)
alreadyMentioned[name] = true
}
}
}
return names
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
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"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
}
// ToJson convert a Channel to a json string
func (c *ChannelSearch) ToJson() string {
b, _ := json.Marshal(c)
return string(b)
}
// ChannelSearchFromJson will decode the input and return a Channel
func ChannelSearchFromJson(data io.Reader) *ChannelSearch {
var cs *ChannelSearch
json.NewDecoder(data).Decode(&cs)
return cs
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ChannelStats struct {
ChannelId string `json:"channel_id"`
MemberCount int64 `json:"member_count"`
GuestCount int64 `json:"guest_count"`
PinnedPostCount int64 `json:"pinnedpost_count"`
}
func (o *ChannelStats) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelStatsFromJson(data io.Reader) *ChannelStats {
var o *ChannelStats
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ChannelView struct {
ChannelId string `json:"channel_id"`
PrevChannelId string `json:"prev_channel_id"`
}
func (o *ChannelView) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelViewFromJson(data io.Reader) *ChannelView {
var o *ChannelView
json.NewDecoder(data).Decode(&o)
return o
}
type ChannelViewResponse struct {
Status string `json:"status"`
LastViewedAtTimes map[string]int64 `json:"last_viewed_at_times"`
}
func (o *ChannelViewResponse) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ChannelViewResponseFromJson(data io.Reader) *ChannelViewResponse {
var o *ChannelViewResponse
json.NewDecoder(data).Decode(&o)
return o
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"os"
)
const (
CDS_OFFLINE_AFTER_MILLIS = 1000 * 60 * 30 // 30 minutes
CDS_TYPE_APP = "mattermost_app"
)
type ClusterDiscovery struct {
Id string `json:"id"`
Type string `json:"type"`
ClusterName string `json:"cluster_name"`
Hostname string `json:"hostname"`
GossipPort int32 `json:"gossip_port"`
Port int32 `json:"port"`
CreateAt int64 `json:"create_at"`
LastPingAt int64 `json:"last_ping_at"`
}
func (o *ClusterDiscovery) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
o.LastPingAt = o.CreateAt
}
}
func (o *ClusterDiscovery) AutoFillHostname() {
// attempt to set the hostname from the OS
if len(o.Hostname) == 0 {
if hn, err := os.Hostname(); err == nil {
o.Hostname = hn
}
}
}
func (o *ClusterDiscovery) AutoFillIpAddress(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 {
o.Hostname = ipAddress
} else {
o.Hostname = GetServerIpAddress(iface)
}
}
}
func (o *ClusterDiscovery) IsEqual(in *ClusterDiscovery) bool {
if in == nil {
return false
}
if o.Type != in.Type {
return false
}
if o.ClusterName != in.ClusterName {
return false
}
if o.Hostname != in.Hostname {
return false
}
return true
}
func FilterClusterDiscovery(vs []*ClusterDiscovery, f func(*ClusterDiscovery) bool) []*ClusterDiscovery {
copy := make([]*ClusterDiscovery, 0)
for _, v := range vs {
if f(v) {
copy = append(copy, v)
}
}
return copy
}
func (o *ClusterDiscovery) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ClusterName) == 0 {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Type) == 0 {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.type.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Hostname) == 0 {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.hostname.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if o.LastPingAt == 0 {
return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.last_ping_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *ClusterDiscovery) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func ClusterDiscoveryFromJson(data io.Reader) *ClusterDiscovery {
decoder := json.NewDecoder(data)
var me ClusterDiscovery
err := decoder.Decode(&me)
if err == nil {
return &me
}
return nil
}

View File

@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ClusterInfo struct {
Id string `json:"id"`
Version string `json:"version"`
ConfigHash string `json:"config_hash"`
IpAddress string `json:"ipaddress"`
Hostname string `json:"hostname"`
}
func (me *ClusterInfo) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
var me *ClusterInfo
json.NewDecoder(data).Decode(&me)
return me
}
func ClusterInfosToJson(objmap []*ClusterInfo) string {
b, _ := json.Marshal(objmap)
return string(b)
}
func ClusterInfosFromJson(data io.Reader) []*ClusterInfo {
decoder := json.NewDecoder(data)
var objmap []*ClusterInfo
if err := decoder.Decode(&objmap); err != nil {
return make([]*ClusterInfo, 0)
} else {
return objmap
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
CLUSTER_EVENT_PUBLISH = "publish"
CLUSTER_EVENT_UPDATE_STATUS = "update_status"
CLUSTER_EVENT_INVALIDATE_ALL_CACHES = "inv_all_caches"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS = "inv_reactions"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOK = "inv_webhook"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS = "inv_channel_posts"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS = "inv_channel_members_notify_props"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS = "inv_channel_members"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME = "inv_channel_name"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_GUEST_COUNT = "inv_channel_guest_count"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER = "inv_user"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER_TEAMS = "inv_user_teams"
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLE_PERMISSIONS = "inv_role_permissions"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_BY_IDS = "inv_profile_ids"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_IN_CHANNEL = "inv_profile_in_channel"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_FILE_INFOS = "inv_file_infos"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOKS = "inv_webhooks"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID = "inv_emojis_by_id"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME = "inv_emojis_id_by_name"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_PINNEDPOSTS_COUNTS = "inv_channel_pinnedposts_counts"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS = "inv_channel_member_counts"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POSTS = "inv_last_posts"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POST_TIME = "inv_last_post_time"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_TEAMS = "inv_teams"
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_INVALIDATE_CACHE_FOR_TERMS_OF_SERVICE = "inv_terms_of_service"
CLUSTER_EVENT_BUSY_STATE_CHANGED = "busy_state_change"
// SendTypes for ClusterMessage.
CLUSTER_SEND_BEST_EFFORT = "best_effort"
CLUSTER_SEND_RELIABLE = "reliable"
)
type ClusterMessage struct {
Event string `json:"event"`
SendType string `json:"-"`
WaitForAllToSend bool `json:"-"`
Data string `json:"data,omitempty"`
Props map[string]string `json:"props,omitempty"`
}
func (o *ClusterMessage) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ClusterMessageFromJson(data io.Reader) *ClusterMessage {
var o *ClusterMessage
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ClusterStats struct {
Id string `json:"id"`
TotalWebsocketConnections int `json:"total_websocket_connections"`
TotalReadDbConnections int `json:"total_read_db_connections"`
TotalMasterDbConnections int `json:"total_master_db_connections"`
}
func (me *ClusterStats) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func ClusterStatsFromJson(data io.Reader) *ClusterStats {
var me *ClusterStats
json.NewDecoder(data).Decode(&me)
return me
}

View File

@ -0,0 +1,148 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"strings"
)
const (
COMMAND_METHOD_POST = "P"
COMMAND_METHOD_GET = "G"
MIN_TRIGGER_LENGTH = 1
MAX_TRIGGER_LENGTH = 128
)
type Command struct {
Id string `json:"id"`
Token string `json:"token"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
CreatorId string `json:"creator_id"`
TeamId string `json:"team_id"`
Trigger string `json:"trigger"`
Method string `json:"method"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
AutoComplete bool `json:"auto_complete"`
AutoCompleteDesc string `json:"auto_complete_desc"`
AutoCompleteHint string `json:"auto_complete_hint"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
URL string `json:"url"`
AutocompleteData *AutocompleteData `db:"-" json:"autocomplete_data,omitempty"`
// AutocompleteIconData is a base64 encoded svg
AutocompleteIconData string `db:"-" json:"autocomplete_icon_data,omitempty"`
}
func (o *Command) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func CommandFromJson(data io.Reader) *Command {
var o *Command
json.NewDecoder(data).Decode(&o)
return o
}
func CommandListToJson(l []*Command) string {
b, _ := json.Marshal(l)
return string(b)
}
func CommandListFromJson(data io.Reader) []*Command {
var o []*Command
json.NewDecoder(data).Decode(&o)
return o
}
func (o *Command) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("Command.IsValid", "model.command.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Token) != 26 {
return NewAppError("Command.IsValid", "model.command.is_valid.token.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("Command.IsValid", "model.command.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("Command.IsValid", "model.command.is_valid.update_at.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.CreatorId) {
return NewAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.TeamId) {
return NewAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Trigger) < MIN_TRIGGER_LENGTH || len(o.Trigger) > MAX_TRIGGER_LENGTH || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") {
return NewAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "", http.StatusBadRequest)
}
if len(o.URL) == 0 || len(o.URL) > 1024 {
return NewAppError("Command.IsValid", "model.command.is_valid.url.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidHttpUrl(o.URL) {
return NewAppError("Command.IsValid", "model.command.is_valid.url_http.app_error", nil, "", http.StatusBadRequest)
}
if !(o.Method == COMMAND_METHOD_GET || o.Method == COMMAND_METHOD_POST) {
return NewAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "", http.StatusBadRequest)
}
if len(o.DisplayName) > 64 {
return NewAppError("Command.IsValid", "model.command.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Description) > 128 {
return NewAppError("Command.IsValid", "model.command.is_valid.description.app_error", nil, "", http.StatusBadRequest)
}
if o.AutocompleteData != nil {
if err := o.AutocompleteData.IsValid(); err != nil {
return NewAppError("Command.IsValid", "model.command.is_valid.autocomplete_data.app_error", nil, err.Error(), http.StatusBadRequest)
}
}
return nil
}
func (o *Command) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.Token == "" {
o.Token = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
}
func (o *Command) PreUpdate() {
o.UpdateAt = GetMillis()
}
func (o *Command) Sanitize() {
o.Token = ""
o.CreatorId = ""
o.Method = ""
o.URL = ""
o.Username = ""
o.IconURL = ""
}

View File

@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
goi18n "github.com/mattermost/go-i18n/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:"-"`
Session Session `json:"-"`
UserMentions UserMentionMap `json:"-"`
ChannelMentions ChannelMentionMap `json:"-"`
}
func (o *CommandArgs) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func CommandArgsFromJson(data io.Reader) *CommandArgs {
var o *CommandArgs
json.NewDecoder(data).Decode(&o)
return o
}
// AddUserMention adds or overrides an entry in UserMentions with name username
// and identifier userId
func (o *CommandArgs) AddUserMention(username, userId string) {
if o.UserMentions == nil {
o.UserMentions = make(UserMentionMap)
}
o.UserMentions[username] = userId
}
// AddChannelMention adds or overrides an entry in ChannelMentions with name
// channelName and identifier channelId
func (o *CommandArgs) AddChannelMention(channelName, channelId string) {
if o.ChannelMentions == nil {
o.ChannelMentions = make(ChannelMentionMap)
}
o.ChannelMentions[channelName] = channelId
}

View File

@ -0,0 +1,455 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/url"
"path"
"reflect"
"strings"
"github.com/pkg/errors"
)
// AutocompleteArgType describes autocomplete argument type
type AutocompleteArgType string
// Argument types
const (
AutocompleteArgTypeText AutocompleteArgType = "TextInput"
AutocompleteArgTypeStaticList AutocompleteArgType = "StaticList"
AutocompleteArgTypeDynamicList AutocompleteArgType = "DynamicList"
)
// AutocompleteData describes slash command autocomplete information.
type AutocompleteData struct {
// Trigger of the command
Trigger string
// Hint of a command
Hint string
// Text displayed to the user to help with the autocomplete description
HelpText string
// Role of the user who should be able to see the autocomplete info of this command
RoleID string
// Arguments of the command. Arguments can be named or positional.
// If they are positional order in the list matters, if they are named order does not matter.
// All arguments should be either named or positional, no mixing allowed.
Arguments []*AutocompleteArg
// Subcommands of the command
SubCommands []*AutocompleteData
}
// AutocompleteArg describes an argument of the command. Arguments can be named or positional.
// If Name is empty string Argument is positional otherwise it is named argument.
// Named arguments are passed as --Name Argument_Value.
type AutocompleteArg struct {
// Name of the argument
Name string
// Text displayed to the user to help with the autocomplete
HelpText string
// Type of the argument
Type AutocompleteArgType
// Required determins if argument is optional or not.
Required bool
// Actual data of the argument (depends on the Type)
Data interface{}
}
// AutocompleteTextArg describes text user can input as an argument.
type AutocompleteTextArg struct {
// Hint of the input text
Hint string
// Regex pattern to match
Pattern string
}
// AutocompleteListItem describes an item in the AutocompleteStaticListArg.
type AutocompleteListItem struct {
Item string
Hint string
HelpText string
}
// AutocompleteStaticListArg is used to input one of the arguments from the list,
// for example [yes, no], [on, off], and so on.
type AutocompleteStaticListArg struct {
PossibleArguments []AutocompleteListItem
}
// AutocompleteDynamicListArg is used when user wants to download possible argument list from the URL.
type AutocompleteDynamicListArg struct {
FetchURL string
}
// AutocompleteSuggestion describes a single suggestion item sent to the front-end
// Example: for user input `/jira cre` -
// Complete might be `/jira create`
// Suggestion might be `create`,
// Hint might be `[issue text]`,
// Description might be `Create a new Issue`
type AutocompleteSuggestion struct {
// Complete describes completed suggestion
Complete string
// Suggestion describes what user might want to input next
Suggestion string
// Hint describes a hint about the suggested input
Hint string
// Description of the command or a suggestion
Description string
// IconData is base64 encoded svg image
IconData string
}
// NewAutocompleteData returns new Autocomplete data.
func NewAutocompleteData(trigger, hint, helpText string) *AutocompleteData {
return &AutocompleteData{
Trigger: trigger,
Hint: hint,
HelpText: helpText,
RoleID: SYSTEM_USER_ROLE_ID,
Arguments: []*AutocompleteArg{},
SubCommands: []*AutocompleteData{},
}
}
// AddCommand adds a subcommand to the autocomplete data.
func (ad *AutocompleteData) AddCommand(command *AutocompleteData) {
ad.SubCommands = append(ad.SubCommands, command)
}
// AddTextArgument adds positional AutocompleteArgTypeText argument to the command.
func (ad *AutocompleteData) AddTextArgument(helpText, hint, pattern string) {
ad.AddNamedTextArgument("", helpText, hint, pattern, true)
}
// AddNamedTextArgument adds named AutocompleteArgTypeText argument to the command.
func (ad *AutocompleteData) AddNamedTextArgument(name, helpText, hint, pattern string, required bool) {
argument := AutocompleteArg{
Name: name,
HelpText: helpText,
Type: AutocompleteArgTypeText,
Required: required,
Data: &AutocompleteTextArg{Hint: hint, Pattern: pattern},
}
ad.Arguments = append(ad.Arguments, &argument)
}
// AddStaticListArgument adds positional AutocompleteArgTypeStaticList argument to the command.
func (ad *AutocompleteData) AddStaticListArgument(helpText string, required bool, items []AutocompleteListItem) {
ad.AddNamedStaticListArgument("", helpText, required, items)
}
// AddNamedStaticListArgument adds named AutocompleteArgTypeStaticList argument to the command.
func (ad *AutocompleteData) AddNamedStaticListArgument(name, helpText string, required bool, items []AutocompleteListItem) {
argument := AutocompleteArg{
Name: name,
HelpText: helpText,
Type: AutocompleteArgTypeStaticList,
Required: required,
Data: &AutocompleteStaticListArg{PossibleArguments: items},
}
ad.Arguments = append(ad.Arguments, &argument)
}
// AddDynamicListArgument adds positional AutocompleteArgTypeDynamicList argument to the command.
func (ad *AutocompleteData) AddDynamicListArgument(helpText, url string, required bool) {
ad.AddNamedDynamicListArgument("", helpText, url, required)
}
// AddNamedDynamicListArgument adds named AutocompleteArgTypeDynamicList argument to the command.
func (ad *AutocompleteData) AddNamedDynamicListArgument(name, helpText, url string, required bool) {
argument := AutocompleteArg{
Name: name,
HelpText: helpText,
Type: AutocompleteArgTypeDynamicList,
Required: required,
Data: &AutocompleteDynamicListArg{FetchURL: url},
}
ad.Arguments = append(ad.Arguments, &argument)
}
// Equals method checks if command is the same.
func (ad *AutocompleteData) Equals(command *AutocompleteData) bool {
if !(ad.Trigger == command.Trigger && ad.HelpText == command.HelpText && ad.RoleID == command.RoleID && ad.Hint == command.Hint) {
return false
}
if len(ad.Arguments) != len(command.Arguments) || len(ad.SubCommands) != len(command.SubCommands) {
return false
}
for i := range ad.Arguments {
if !ad.Arguments[i].Equals(command.Arguments[i]) {
return false
}
}
for i := range ad.SubCommands {
if !ad.SubCommands[i].Equals(command.SubCommands[i]) {
return false
}
}
return true
}
// UpdateRelativeURLsForPluginCommands method updates relative urls for plugin commands
func (ad *AutocompleteData) UpdateRelativeURLsForPluginCommands(baseURL *url.URL) error {
for _, arg := range ad.Arguments {
if arg.Type != AutocompleteArgTypeDynamicList {
continue
}
dynamicList, ok := arg.Data.(*AutocompleteDynamicListArg)
if !ok {
return errors.New("Not a proper DynamicList type argument")
}
dynamicListURL, err := url.Parse(dynamicList.FetchURL)
if err != nil {
return errors.Wrapf(err, "FetchURL is not a proper url")
}
if !dynamicListURL.IsAbs() {
absURL := &url.URL{}
*absURL = *baseURL
absURL.Path = path.Join(absURL.Path, dynamicList.FetchURL)
dynamicList.FetchURL = absURL.String()
}
}
for _, command := range ad.SubCommands {
err := command.UpdateRelativeURLsForPluginCommands(baseURL)
if err != nil {
return err
}
}
return nil
}
// IsValid method checks if autocomplete data is valid.
func (ad *AutocompleteData) IsValid() error {
if ad == nil {
return errors.New("No nil commands are allowed in AutocompleteData")
}
if ad.Trigger == "" {
return errors.New("An empty command name in the autocomplete data")
}
if strings.ToLower(ad.Trigger) != ad.Trigger {
return errors.New("Command should be lowercase")
}
roles := []string{SYSTEM_ADMIN_ROLE_ID, SYSTEM_USER_ROLE_ID, ""}
if stringNotInSlice(ad.RoleID, roles) {
return errors.New("Wrong role in the autocomplete data")
}
if len(ad.Arguments) > 0 && len(ad.SubCommands) > 0 {
return errors.New("Command can't have arguments and subcommands")
}
if len(ad.Arguments) > 0 {
namedArgumentIndex := -1
for i, arg := range ad.Arguments {
if arg.Name != "" { // it's a named argument
if namedArgumentIndex == -1 { // first named argument
namedArgumentIndex = i
}
} else { // it's a positional argument
if namedArgumentIndex != -1 {
return errors.New("Named argument should not be before positional argument")
}
}
if arg.Type == AutocompleteArgTypeDynamicList {
dynamicList, ok := arg.Data.(*AutocompleteDynamicListArg)
if !ok {
return errors.New("Not a proper DynamicList type argument")
}
_, err := url.Parse(dynamicList.FetchURL)
if err != nil {
return errors.Wrapf(err, "FetchURL is not a proper url")
}
} else if arg.Type == AutocompleteArgTypeStaticList {
staticList, ok := arg.Data.(*AutocompleteStaticListArg)
if !ok {
return errors.New("Not a proper StaticList type argument")
}
for _, arg := range staticList.PossibleArguments {
if arg.Item == "" {
return errors.New("Possible argument name not set in StaticList argument")
}
}
} else if arg.Type == AutocompleteArgTypeText {
if _, ok := arg.Data.(*AutocompleteTextArg); !ok {
return errors.New("Not a proper TextInput type argument")
}
if arg.Name == "" && !arg.Required {
return errors.New("Positional argument can not be optional")
}
}
}
}
for _, command := range ad.SubCommands {
err := command.IsValid()
if err != nil {
return err
}
}
return nil
}
// ToJSON encodes AutocompleteData struct to the json
func (ad *AutocompleteData) ToJSON() ([]byte, error) {
b, err := json.Marshal(ad)
if err != nil {
return nil, errors.Wrapf(err, "can't marshal slash command %s", ad.Trigger)
}
return b, nil
}
// AutocompleteDataFromJSON decodes AutocompleteData struct from the json
func AutocompleteDataFromJSON(data []byte) (*AutocompleteData, error) {
var ad AutocompleteData
if err := json.Unmarshal(data, &ad); err != nil {
return nil, errors.Wrap(err, "can't unmarshal AutocompleteData")
}
return &ad, nil
}
// Equals method checks if argument is the same.
func (a *AutocompleteArg) Equals(arg *AutocompleteArg) bool {
if a.Name != arg.Name ||
a.HelpText != arg.HelpText ||
a.Type != arg.Type ||
a.Required != arg.Required ||
!reflect.DeepEqual(a.Data, arg.Data) {
return false
}
return true
}
// UnmarshalJSON will unmarshal argument
func (a *AutocompleteArg) UnmarshalJSON(b []byte) error {
var arg map[string]interface{}
if err := json.Unmarshal(b, &arg); err != nil {
return errors.Wrapf(err, "Can't unmarshal argument %s", string(b))
}
var ok bool
a.Name, ok = arg["Name"].(string)
if !ok {
return errors.Errorf("No field Name in the argument %s", string(b))
}
a.HelpText, ok = arg["HelpText"].(string)
if !ok {
return errors.Errorf("No field HelpText in the argument %s", string(b))
}
t, ok := arg["Type"].(string)
if !ok {
return errors.Errorf("No field Type in the argument %s", string(b))
}
a.Type = AutocompleteArgType(t)
a.Required, ok = arg["Required"].(bool)
if !ok {
return errors.Errorf("No field Required in the argument %s", string(b))
}
data, ok := arg["Data"]
if !ok {
return errors.Errorf("No field Data in the argument %s", string(b))
}
if a.Type == AutocompleteArgTypeText {
m, ok := data.(map[string]interface{})
if !ok {
return errors.Errorf("Wrong Data type in the TextInput argument %s", string(b))
}
pattern, ok := m["Pattern"].(string)
if !ok {
return errors.Errorf("No field Pattern in the TextInput argument %s", string(b))
}
hint, ok := m["Hint"].(string)
if !ok {
return errors.Errorf("No field Hint in the TextInput argument %s", string(b))
}
a.Data = &AutocompleteTextArg{Hint: hint, Pattern: pattern}
} else if a.Type == AutocompleteArgTypeStaticList {
m, ok := data.(map[string]interface{})
if !ok {
return errors.Errorf("Wrong Data type in the StaticList argument %s", string(b))
}
list, ok := m["PossibleArguments"].([]interface{})
if !ok {
return errors.Errorf("No field PossibleArguments in the StaticList argument %s", string(b))
}
possibleArguments := []AutocompleteListItem{}
for i := range list {
args, ok := list[i].(map[string]interface{})
if !ok {
return errors.Errorf("Wrong AutocompleteStaticListItem type in the StaticList argument %s", string(b))
}
item, ok := args["Item"].(string)
if !ok {
return errors.Errorf("No field Item in the StaticList's possible arguments %s", string(b))
}
hint, ok := args["Hint"].(string)
if !ok {
return errors.Errorf("No field Hint in the StaticList's possible arguments %s", string(b))
}
helpText, ok := args["HelpText"].(string)
if !ok {
return errors.Errorf("No field Hint in the StaticList's possible arguments %s", string(b))
}
possibleArguments = append(possibleArguments, AutocompleteListItem{
Item: item,
Hint: hint,
HelpText: helpText,
})
}
a.Data = &AutocompleteStaticListArg{PossibleArguments: possibleArguments}
} else if a.Type == AutocompleteArgTypeDynamicList {
m, ok := data.(map[string]interface{})
if !ok {
return errors.Errorf("Wrong type in the DynamicList argument %s", string(b))
}
url, ok := m["FetchURL"].(string)
if !ok {
return errors.Errorf("No field FetchURL in the DynamicList's argument %s", string(b))
}
a.Data = &AutocompleteDynamicListArg{FetchURL: url}
}
return nil
}
// AutocompleteSuggestionsToJSON returns json for a list of AutocompleteSuggestion objects
func AutocompleteSuggestionsToJSON(suggestions []AutocompleteSuggestion) []byte {
b, _ := json.Marshal(suggestions)
return b
}
// AutocompleteSuggestionsFromJSON returns list of AutocompleteSuggestions from json.
func AutocompleteSuggestionsFromJSON(data io.Reader) []AutocompleteSuggestion {
var o []AutocompleteSuggestion
json.NewDecoder(data).Decode(&o)
return o
}
// AutocompleteStaticListItemsToJSON returns json for a list of AutocompleteStaticListItem objects
func AutocompleteStaticListItemsToJSON(items []AutocompleteListItem) []byte {
b, _ := json.Marshal(items)
return b
}
// AutocompleteStaticListItemsFromJSON returns list of AutocompleteStaticListItem from json.
func AutocompleteStaticListItemsFromJSON(data io.Reader) []AutocompleteListItem {
var o []AutocompleteListItem
json.NewDecoder(data).Decode(&o)
return o
}
func stringNotInSlice(a string, slice []string) bool {
for _, b := range slice {
if b == a {
return false
}
}
return true
}

View File

@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type CommandMoveRequest struct {
TeamId string `json:"team_id"`
}
func CommandMoveRequestFromJson(data io.Reader) (*CommandMoveRequest, error) {
decoder := json.NewDecoder(data)
var cmr CommandMoveRequest
err := decoder.Decode(&cmr)
if err != nil {
return nil, err
}
return &cmr, nil
}
func (cmr *CommandMoveRequest) ToJson() string {
b, err := json.Marshal(cmr)
if err != nil {
return ""
}
return string(b)
}

View File

@ -0,0 +1,77 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"io/ioutil"
"strings"
"github.com/mattermost/mattermost-server/v5/utils/jsonutils"
)
const (
COMMAND_RESPONSE_TYPE_IN_CHANNEL = "in_channel"
COMMAND_RESPONSE_TYPE_EPHEMERAL = "ephemeral"
)
type CommandResponse struct {
ResponseType string `json:"response_type"`
Text string `json:"text"`
Username string `json:"username"`
ChannelId string `json:"channel_id"`
IconURL string `json:"icon_url"`
Type string `json:"type"`
Props StringInterface `json:"props"`
GotoLocation string `json:"goto_location"`
TriggerId string `json:"trigger_id"`
SkipSlackParsing bool `json:"skip_slack_parsing"` // Set to `true` to skip the Slack-compatibility handling of Text.
Attachments []*SlackAttachment `json:"attachments"`
ExtraResponses []*CommandResponse `json:"extra_responses"`
}
func (o *CommandResponse) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func CommandResponseFromHTTPBody(contentType string, body io.Reader) (*CommandResponse, error) {
if strings.TrimSpace(strings.Split(contentType, ";")[0]) == "application/json" {
return CommandResponseFromJson(body)
}
if b, err := ioutil.ReadAll(body); err == nil {
return CommandResponseFromPlainText(string(b)), nil
}
return nil, nil
}
func CommandResponseFromPlainText(text string) *CommandResponse {
return &CommandResponse{
Text: text,
}
}
func CommandResponseFromJson(data io.Reader) (*CommandResponse, error) {
b, err := ioutil.ReadAll(data)
if err != nil {
return nil, err
}
var o CommandResponse
err = json.Unmarshal(b, &o)
if err != nil {
return nil, jsonutils.HumanizeJsonError(err, b)
}
o.Attachments = StringifySlackFieldValue(o.Attachments)
if o.ExtraResponses != nil {
for _, resp := range o.ExtraResponses {
resp.Attachments = StringifySlackFieldValue(resp.Attachments)
}
}
return &o, nil
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"net/http"
)
type CommandWebhook struct {
Id string
CreateAt int64
CommandId string
UserId string
ChannelId string
RootId string
ParentId string
UseCount int
}
const (
COMMAND_WEBHOOK_LIFETIME = 1000 * 60 * 30
)
func (o *CommandWebhook) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
}
}
func (o *CommandWebhook) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidId(o.CommandId) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.command_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.UserId) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.user_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.ChannelId) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.RootId) != 0 && !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) {
return NewAppError("CommandWebhook.IsValid", "model.command_hook.parent_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}

View File

@ -0,0 +1,119 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"strings"
)
const (
COMPLIANCE_STATUS_CREATED = "created"
COMPLIANCE_STATUS_RUNNING = "running"
COMPLIANCE_STATUS_FINISHED = "finished"
COMPLIANCE_STATUS_FAILED = "failed"
COMPLIANCE_STATUS_REMOVED = "removed"
COMPLIANCE_TYPE_DAILY = "daily"
COMPLIANCE_TYPE_ADHOC = "adhoc"
)
type Compliance struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UserId string `json:"user_id"`
Status string `json:"status"`
Count int `json:"count"`
Desc string `json:"desc"`
Type string `json:"type"`
StartAt int64 `json:"start_at"`
EndAt int64 `json:"end_at"`
Keywords string `json:"keywords"`
Emails string `json:"emails"`
}
type Compliances []Compliance
func (c *Compliance) ToJson() string {
b, _ := json.Marshal(c)
return string(b)
}
func (c *Compliance) PreSave() {
if c.Id == "" {
c.Id = NewId()
}
if c.Status == "" {
c.Status = COMPLIANCE_STATUS_CREATED
}
c.Count = 0
c.Emails = NormalizeEmail(c.Emails)
c.Keywords = strings.ToLower(c.Keywords)
c.CreateAt = GetMillis()
}
func (c *Compliance) JobName() string {
jobName := c.Type
if c.Type == COMPLIANCE_TYPE_DAILY {
jobName += "-" + c.Desc
}
jobName += "-" + c.Id
return jobName
}
func (c *Compliance) IsValid() *AppError {
if !IsValidId(c.Id) {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if c.CreateAt == 0 {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
if len(c.Desc) > 512 || len(c.Desc) == 0 {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "", http.StatusBadRequest)
}
if c.StartAt == 0 {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_at.app_error", nil, "", http.StatusBadRequest)
}
if c.EndAt == 0 {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.end_at.app_error", nil, "", http.StatusBadRequest)
}
if c.EndAt <= c.StartAt {
return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_end_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func ComplianceFromJson(data io.Reader) *Compliance {
var c *Compliance
json.NewDecoder(data).Decode(&c)
return c
}
func (c Compliances) ToJson() string {
if b, err := json.Marshal(c); err != nil {
return "[]"
} else {
return string(b)
}
}
func CompliancesFromJson(data io.Reader) Compliances {
var o Compliances
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,126 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"regexp"
"time"
)
type CompliancePost struct {
// From Team
TeamName string
TeamDisplayName string
// From Channel
ChannelName string
ChannelDisplayName string
ChannelType string
// From User
UserUsername string
UserEmail string
UserNickname string
// From Post
PostId string
PostCreateAt int64
PostUpdateAt int64
PostDeleteAt int64
PostRootId string
PostParentId string
PostOriginalId string
PostMessage string
PostType string
PostProps string
PostHashtags string
PostFileIds string
IsBot bool
}
func CompliancePostHeader() []string {
return []string{
"TeamName",
"TeamDisplayName",
"ChannelName",
"ChannelDisplayName",
"ChannelType",
"UserUsername",
"UserEmail",
"UserNickname",
"PostId",
"PostCreateAt",
"PostUpdateAt",
"PostDeleteAt",
"PostRootId",
"PostParentId",
"PostOriginalId",
"PostMessage",
"PostType",
"PostProps",
"PostHashtags",
"PostFileIds",
"UserType",
}
}
func cleanComplianceStrings(in string) string {
if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched {
return "'" + in
} else {
return in
}
}
func (me *CompliancePost) Row() []string {
postDeleteAt := ""
if me.PostDeleteAt > 0 {
postDeleteAt = time.Unix(0, me.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)
}
userType := "user"
if me.IsBot {
userType = "bot"
}
return []string{
cleanComplianceStrings(me.TeamName),
cleanComplianceStrings(me.TeamDisplayName),
cleanComplianceStrings(me.ChannelName),
cleanComplianceStrings(me.ChannelDisplayName),
cleanComplianceStrings(me.ChannelType),
cleanComplianceStrings(me.UserUsername),
cleanComplianceStrings(me.UserEmail),
cleanComplianceStrings(me.UserNickname),
userType,
me.PostId,
time.Unix(0, me.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,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type DataRetentionPolicy 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)
}
func DataRetentionPolicyFromJson(data io.Reader) *DataRetentionPolicy {
var me *DataRetentionPolicy
json.NewDecoder(data).Decode(&me)
return me
}

View File

@ -0,0 +1,96 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"regexp"
)
const (
EMOJI_NAME_MAX_LENGTH = 64
EMOJI_SORT_BY_NAME = "name"
)
var EMOJI_PATTERN = regexp.MustCompile(`:[a-zA-Z0-9_-]+:`)
type Emoji struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
CreatorId string `json:"creator_id"`
Name string `json:"name"`
}
func inSystemEmoji(emojiName string) bool {
_, ok := SystemEmojis[emojiName]
return ok
}
func GetSystemEmojiId(emojiName string) (string, bool) {
id, found := SystemEmojis[emojiName]
return id, found
}
func (emoji *Emoji) IsValid() *AppError {
if !IsValidId(emoji.Id) {
return NewAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "", http.StatusBadRequest)
}
if emoji.CreateAt == 0 {
return NewAppError("Emoji.IsValid", "model.emoji.create_at.app_error", nil, "id="+emoji.Id, http.StatusBadRequest)
}
if emoji.UpdateAt == 0 {
return NewAppError("Emoji.IsValid", "model.emoji.update_at.app_error", nil, "id="+emoji.Id, http.StatusBadRequest)
}
if len(emoji.CreatorId) > 26 {
return NewAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "", http.StatusBadRequest)
}
return IsValidEmojiName(emoji.Name)
}
func IsValidEmojiName(name string) *AppError {
if len(name) == 0 || len(name) > EMOJI_NAME_MAX_LENGTH || !IsValidAlphaNumHyphenUnderscore(name, false) || inSystemEmoji(name) {
return NewAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (emoji *Emoji) PreSave() {
if emoji.Id == "" {
emoji.Id = NewId()
}
emoji.CreateAt = GetMillis()
emoji.UpdateAt = emoji.CreateAt
}
func (emoji *Emoji) ToJson() string {
b, _ := json.Marshal(emoji)
return string(b)
}
func EmojiFromJson(data io.Reader) *Emoji {
var emoji *Emoji
json.NewDecoder(data).Decode(&emoji)
return emoji
}
func EmojiListToJson(emojiList []*Emoji) string {
b, _ := json.Marshal(emojiList)
return string(b)
}
func EmojiListFromJson(data io.Reader) []*Emoji {
var emojiList []*Emoji
json.NewDecoder(data).Decode(&emojiList)
return emojiList
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type EmojiSearch struct {
Term string `json:"term"`
PrefixOnly bool `json:"prefix_only"`
}
func (es *EmojiSearch) ToJson() string {
b, _ := json.Marshal(es)
return string(b)
}
func EmojiSearchFromJson(data io.Reader) *EmojiSearch {
var es *EmojiSearch
json.NewDecoder(data).Decode(&es)
return es
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
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"`
}
func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse {
var o *FileUploadResponse
json.NewDecoder(data).Decode(&o)
return o
}
func (o *FileUploadResponse) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}

View File

@ -0,0 +1,209 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"bytes"
"encoding/json"
"image"
"image/gif"
"io"
"mime"
"net/http"
"path/filepath"
"strings"
)
const (
FILEINFO_SORT_BY_CREATED = "CreateAt"
FILEINFO_SORT_BY_SIZE = "Size"
)
// GetFileInfosOptions contains options for getting FileInfos
type GetFileInfosOptions struct {
// UserIds optionally limits the FileInfos to those created by the given users.
UserIds []string `json:"user_ids"`
// ChannelIds optionally limits the FileInfos to those created in the given channels.
ChannelIds []string `json:"channel_ids"`
// Since optionally limits FileInfos to those created at or after the given time, specified as Unix time in milliseconds.
Since int64 `json:"since"`
// IncludeDeleted if set includes deleted FileInfos.
IncludeDeleted bool `json:"include_deleted"`
// SortBy sorts the FileInfos by this field. The default is to sort by date created.
SortBy string `json:"sort_by"`
// SortDescending changes the sort direction to descending order when true.
SortDescending bool `json:"sort_descending"`
}
type FileInfo struct {
Id string `json:"id"`
CreatorId string `json:"user_id"`
PostId string `json:"post_id,omitempty"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
Path string `json:"-"` // not sent back to the client
ThumbnailPath string `json:"-"` // not sent back to the client
PreviewPath string `json:"-"` // not sent back to the client
Name string `json:"name"`
Extension string `json:"extension"`
Size int64 `json:"size"`
MimeType string `json:"mime_type"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
HasPreviewImage bool `json:"has_preview_image,omitempty"`
}
func (fi *FileInfo) ToJson() string {
b, _ := json.Marshal(fi)
return string(b)
}
func FileInfoFromJson(data io.Reader) *FileInfo {
decoder := json.NewDecoder(data)
var fi FileInfo
if err := decoder.Decode(&fi); err != nil {
return nil
} else {
return &fi
}
}
func FileInfosToJson(infos []*FileInfo) string {
b, _ := json.Marshal(infos)
return string(b)
}
func FileInfosFromJson(data io.Reader) []*FileInfo {
decoder := json.NewDecoder(data)
var infos []*FileInfo
if err := decoder.Decode(&infos); err != nil {
return nil
} else {
return infos
}
}
func (fi *FileInfo) PreSave() {
if fi.Id == "" {
fi.Id = NewId()
}
if fi.CreateAt == 0 {
fi.CreateAt = GetMillis()
}
if fi.UpdateAt < fi.CreateAt {
fi.UpdateAt = fi.CreateAt
}
}
func (fi *FileInfo) IsValid() *AppError {
if !IsValidId(fi.Id) {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(fi.CreatorId) && fi.CreatorId != "nouser" {
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) {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
}
if fi.CreateAt == 0 {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
}
if fi.UpdateAt == 0 {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
}
if fi.Path == "" {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
}
return nil
}
func (fi *FileInfo) IsImage() bool {
return strings.HasPrefix(fi.MimeType, "image")
}
func NewInfo(name string) *FileInfo {
info := &FileInfo{
Name: name,
}
extension := strings.ToLower(filepath.Ext(name))
info.MimeType = mime.TypeByExtension(extension)
if extension != "" && extension[0] == '.' {
// The client expects a file extension without the leading period
info.Extension = extension[1:]
} else {
info.Extension = extension
}
return info
}
func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) {
info := &FileInfo{
Name: name,
Size: int64(len(data)),
}
var err *AppError
extension := strings.ToLower(filepath.Ext(name))
info.MimeType = mime.TypeByExtension(extension)
if extension != "" && extension[0] == '.' {
// The client expects a file extension without the leading period
info.Extension = extension[1:]
} else {
info.Extension = extension
}
if info.IsImage() {
// Only set the width and height if it's actually an image that we can understand
if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil {
info.Width = config.Width
info.Height = config.Height
if info.MimeType == "image/gif" {
// Just show the gif itself instead of a preview image for animated gifs
if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
// Still return the rest of the info even though it doesn't appear to be an actual gif
info.HasPreviewImage = true
return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name, http.StatusBadRequest)
} else {
info.HasPreviewImage = len(gifConfig.Image) == 1
}
} else {
info.HasPreviewImage = true
}
}
}
return info, err
}
func GetEtagForFileInfos(infos []*FileInfo) string {
if len(infos) == 0 {
return Etag()
}
var maxUpdateAt int64
for _, info := range infos {
if info.UpdateAt > maxUpdateAt {
maxUpdateAt = info.UpdateAt
}
}
return Etag(infos[0].PostId, maxUpdateAt)
}

View File

@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
const (
USER_AUTH_SERVICE_GITLAB = "gitlab"
)

View File

@ -0,0 +1,210 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"regexp"
)
const (
GroupSourceLdap GroupSource = "ldap"
GroupNameMaxLength = 64
GroupSourceMaxLength = 64
GroupDisplayNameMaxLength = 128
GroupDescriptionMaxLength = 1024
GroupRemoteIDMaxLength = 48
)
type GroupSource string
var allGroupSources = []GroupSource{
GroupSourceLdap,
}
var groupSourcesRequiringRemoteID = []GroupSource{
GroupSourceLdap,
}
type Group struct {
Id string `json:"id"`
Name *string `json:"name,omitempty"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Source GroupSource `json:"source"`
RemoteId string `json:"remote_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
HasSyncables bool `db:"-" json:"has_syncables"`
MemberCount *int `db:"-" json:"member_count,omitempty"`
AllowReference bool `json:"allow_reference"`
}
type GroupWithSchemeAdmin struct {
Group
SchemeAdmin *bool `db:"SyncableSchemeAdmin" json:"scheme_admin,omitempty"`
}
type GroupsAssociatedToChannelWithSchemeAdmin struct {
ChannelId string `json:"channel_id"`
Group
SchemeAdmin *bool `db:"SyncableSchemeAdmin" json:"scheme_admin,omitempty"`
}
type GroupsAssociatedToChannel struct {
ChannelId string `json:"channel_id"`
Groups []*GroupWithSchemeAdmin `json:"groups"`
}
type GroupPatch struct {
Name *string `json:"name"`
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
AllowReference *bool `json:"allow_reference"`
}
type LdapGroupSearchOpts struct {
Q string
IsLinked *bool
IsConfigured *bool
}
type GroupSearchOpts struct {
Q string
NotAssociatedToTeam string
NotAssociatedToChannel string
IncludeMemberCount bool
FilterAllowReference bool
PageOpts *PageOpts
Since int64
// FilterParentTeamPermitted filters the groups to the intersect of the
// set associated to the parent team and those returned by the query.
// If the parent team is not group-constrained or if NotAssociatedToChannel
// is not set then this option is ignored.
FilterParentTeamPermitted bool
}
type PageOpts struct {
Page int
PerPage int
}
func (group *Group) Patch(patch *GroupPatch) {
if patch.Name != nil {
group.Name = patch.Name
}
if patch.DisplayName != nil {
group.DisplayName = *patch.DisplayName
}
if patch.Description != nil {
group.Description = *patch.Description
}
if patch.AllowReference != nil {
group.AllowReference = *patch.AllowReference
}
}
func (group *Group) IsValidForCreate() *AppError {
err := group.IsValidName()
if err != nil {
return err
}
if l := len(group.DisplayName); l == 0 || l > GroupDisplayNameMaxLength {
return NewAppError("Group.IsValidForCreate", "model.group.display_name.app_error", map[string]interface{}{"GroupDisplayNameMaxLength": GroupDisplayNameMaxLength}, "", http.StatusBadRequest)
}
if len(group.Description) > GroupDescriptionMaxLength {
return NewAppError("Group.IsValidForCreate", "model.group.description.app_error", map[string]interface{}{"GroupDescriptionMaxLength": GroupDescriptionMaxLength}, "", http.StatusBadRequest)
}
isValidSource := false
for _, groupSource := range allGroupSources {
if group.Source == groupSource {
isValidSource = true
break
}
}
if !isValidSource {
return NewAppError("Group.IsValidForCreate", "model.group.source.app_error", nil, "", http.StatusBadRequest)
}
if len(group.RemoteId) > GroupRemoteIDMaxLength || (len(group.RemoteId) == 0 && group.requiresRemoteId()) {
return NewAppError("Group.IsValidForCreate", "model.group.remote_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (group *Group) requiresRemoteId() bool {
for _, groupSource := range groupSourcesRequiringRemoteID {
if groupSource == group.Source {
return true
}
}
return false
}
func (group *Group) IsValidForUpdate() *AppError {
if !IsValidId(group.Id) {
return NewAppError("Group.IsValidForUpdate", "model.group.id.app_error", nil, "", http.StatusBadRequest)
}
if group.CreateAt == 0 {
return NewAppError("Group.IsValidForUpdate", "model.group.create_at.app_error", nil, "", http.StatusBadRequest)
}
if group.UpdateAt == 0 {
return NewAppError("Group.IsValidForUpdate", "model.group.update_at.app_error", nil, "", http.StatusBadRequest)
}
if err := group.IsValidForCreate(); err != nil {
return err
}
return nil
}
func (group *Group) ToJson() string {
b, _ := json.Marshal(group)
return string(b)
}
var validGroupnameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
func (group *Group) IsValidName() *AppError {
if group.Name == nil {
if group.AllowReference {
return NewAppError("Group.IsValidName", "model.group.name.app_error", map[string]interface{}{"GroupNameMaxLength": GroupNameMaxLength}, "", http.StatusBadRequest)
}
} else {
if l := len(*group.Name); l == 0 || l > GroupNameMaxLength {
return NewAppError("Group.IsValidName", "model.group.name.invalid_length.app_error", map[string]interface{}{"GroupNameMaxLength": GroupNameMaxLength}, "", http.StatusBadRequest)
}
if !validGroupnameChars.MatchString(*group.Name) {
return NewAppError("Group.IsValidName", "model.group.name.invalid_chars.app_error", nil, "", http.StatusBadRequest)
}
}
return nil
}
func GroupFromJson(data io.Reader) *Group {
var group *Group
json.NewDecoder(data).Decode(&group)
return group
}
func GroupsFromJson(data io.Reader) []*Group {
var groups []*Group
json.NewDecoder(data).Decode(&groups)
return groups
}
func GroupPatchFromJson(data io.Reader) *GroupPatch {
var groupPatch *GroupPatch
json.NewDecoder(data).Decode(&groupPatch)
return groupPatch
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import "net/http"
type GroupMember struct {
GroupId string `json:"group_id"`
UserId string `json:"user_id"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
}
func (gm *GroupMember) IsValid() *AppError {
if !IsValidId(gm.GroupId) {
return NewAppError("GroupMember.IsValid", "model.group_member.group_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(gm.UserId) {
return NewAppError("GroupMember.IsValid", "model.group_member.user_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}

View File

@ -0,0 +1,180 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
)
type GroupSyncableType string
const (
GroupSyncableTypeTeam GroupSyncableType = "Team"
GroupSyncableTypeChannel GroupSyncableType = "Channel"
)
func (gst GroupSyncableType) String() string {
return string(gst)
}
type GroupSyncable struct {
GroupId string `json:"group_id"`
// SyncableId represents the Id of the model that is being synced with the group, for example a ChannelId or
// TeamId.
SyncableId string `db:"-" json:"-"`
AutoAdd bool `json:"auto_add"`
SchemeAdmin bool `json:"scheme_admin"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
UpdateAt int64 `json:"update_at"`
Type GroupSyncableType `db:"-" json:"-"`
// Values joined in from the associated team and/or channel
ChannelDisplayName string `db:"-" json:"-"`
TeamDisplayName string `db:"-" json:"-"`
TeamType string `db:"-" json:"-"`
ChannelType string `db:"-" json:"-"`
TeamID string `db:"-" json:"-"`
}
func (syncable *GroupSyncable) IsValid() *AppError {
if !IsValidId(syncable.GroupId) {
return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.group_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(syncable.SyncableId) {
return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.syncable_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (syncable *GroupSyncable) UnmarshalJSON(b []byte) error {
var kvp map[string]interface{}
err := json.Unmarshal(b, &kvp)
if err != nil {
return err
}
for key, value := range kvp {
switch key {
case "team_id":
syncable.SyncableId = value.(string)
syncable.Type = GroupSyncableTypeTeam
case "channel_id":
syncable.SyncableId = value.(string)
syncable.Type = GroupSyncableTypeChannel
case "group_id":
syncable.GroupId = value.(string)
case "auto_add":
syncable.AutoAdd = value.(bool)
default:
}
}
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"`
*Alias
}{
TeamDisplayName: syncable.TeamDisplayName,
TeamType: syncable.TeamType,
TeamID: syncable.SyncableId,
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"`
TeamID string `json:"team_id,omitempty"`
TeamDisplayName string `json:"team_display_name,omitempty"`
TeamType string `json:"team_type,omitempty"`
*Alias
}{
ChannelID: syncable.SyncableId,
ChannelDisplayName: syncable.ChannelDisplayName,
ChannelType: syncable.ChannelType,
TeamID: syncable.TeamID,
TeamDisplayName: syncable.TeamDisplayName,
TeamType: syncable.TeamType,
Alias: (*Alias)(syncable),
})
default:
return nil, &json.MarshalerError{
Err: fmt.Errorf("unknown syncable type: %s", syncable.Type),
}
}
}
type GroupSyncablePatch struct {
AutoAdd *bool `json:"auto_add"`
SchemeAdmin *bool `json:"scheme_admin"`
}
func (syncable *GroupSyncable) Patch(patch *GroupSyncablePatch) {
if patch.AutoAdd != nil {
syncable.AutoAdd = *patch.AutoAdd
}
if patch.SchemeAdmin != nil {
syncable.SchemeAdmin = *patch.SchemeAdmin
}
}
type UserTeamIDPair struct {
UserID string
TeamID string
}
type UserChannelIDPair struct {
UserID string
ChannelID string
}
func GroupSyncableFromJson(data io.Reader) *GroupSyncable {
groupSyncable := &GroupSyncable{}
bodyBytes, _ := ioutil.ReadAll(data)
json.Unmarshal(bodyBytes, groupSyncable)
return groupSyncable
}
func GroupSyncablesFromJson(data io.Reader) []*GroupSyncable {
groupSyncables := []*GroupSyncable{}
bodyBytes, _ := ioutil.ReadAll(data)
json.Unmarshal(bodyBytes, &groupSyncables)
return groupSyncables
}
func NewGroupTeam(groupID, teamID string, autoAdd bool) *GroupSyncable {
return &GroupSyncable{
GroupId: groupID,
SyncableId: teamID,
Type: GroupSyncableTypeTeam,
AutoAdd: autoAdd,
}
}
func NewGroupChannel(groupID, channelID string, autoAdd bool) *GroupSyncable {
return &GroupSyncable{
GroupId: groupID,
SyncableId: channelID,
Type: GroupSyncableTypeChannel,
AutoAdd: autoAdd,
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
type GuestsInvite struct {
Emails []string `json:"emails"`
Channels []string `json:"channels"`
Message string `json:"message"`
}
// IsValid validates the user and returns an error if it isn't configured
// correctly.
func (i *GuestsInvite) IsValid() *AppError {
if len(i.Emails) == 0 {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.emails.app_error", nil, "", http.StatusBadRequest)
}
for _, email := range i.Emails {
if len(email) > USER_EMAIL_MAX_LENGTH || len(email) == 0 || !IsValidEmail(email) {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.email.app_error", nil, "email="+email, http.StatusBadRequest)
}
}
if len(i.Channels) == 0 {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.channels.app_error", nil, "", http.StatusBadRequest)
}
for _, channel := range i.Channels {
if len(channel) != 26 {
return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.channel.app_error", nil, "channel="+channel, http.StatusBadRequest)
}
}
return nil
}
// GuestsInviteFromJson will decode the input and return a GuestsInvite
func GuestsInviteFromJson(data io.Reader) *GuestsInvite {
var i *GuestsInvite
json.NewDecoder(data).Decode(&i)
return i
}
func (i *GuestsInvite) ToJson() string {
b, _ := json.Marshal(i)
return string(b)
}

View File

@ -0,0 +1,217 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"bytes"
"encoding/json"
"io"
"net/http"
"regexp"
)
const (
DEFAULT_WEBHOOK_USERNAME = "webhook"
)
type IncomingWebhook struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
ChannelLocked bool `json:"channel_locked"`
}
type IncomingWebhookRequest struct {
Text string `json:"text"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
ChannelName string `json:"channel"`
Props StringInterface `json:"props"`
Attachments []*SlackAttachment `json:"attachments"`
Type string `json:"type"`
IconEmoji string `json:"icon_emoji"`
}
func (o *IncomingWebhook) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func IncomingWebhookFromJson(data io.Reader) *IncomingWebhook {
var o *IncomingWebhook
json.NewDecoder(data).Decode(&o)
return o
}
func IncomingWebhookListToJson(l []*IncomingWebhook) string {
b, _ := json.Marshal(l)
return string(b)
}
func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook {
var o []*IncomingWebhook
json.NewDecoder(data).Decode(&o)
return o
}
func (o *IncomingWebhook) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidId(o.UserId) {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.ChannelId) {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.TeamId) {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.DisplayName) > 64 {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Description) > 500 {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Username) > 64 {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.username.app_error", nil, "", http.StatusBadRequest)
}
if len(o.IconURL) > 1024 {
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.icon_url.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *IncomingWebhook) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
}
func (o *IncomingWebhook) PreUpdate() {
o.UpdateAt = GetMillis()
}
// escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice.
// Context:
// JSON strings are not supposed to contain control characters such as \n, \t,
// ... but some incoming webhooks might still send invalid JSON and we want to
// try to handle that. An example invalid JSON string from an incoming webhook
// might look like this (strings for both "text" and "fallback" attributes are
// invalid JSON strings because they contain unescaped newlines and tabs):
// `{
// "text": "this is a test
// that contains a newline and tabs",
// "attachments": [
// {
// "fallback": "Required plain-text summary of the attachment
// that contains a newline and tabs",
// "color": "#36a64f",
// ...
// "text": "Optional text that appears within the attachment
// that contains a newline and tabs",
// ...
// "thumb_url": "http://example.com/path/to/thumb.png"
// }
// ]
// }`
// This function will search for `"key": "value"` pairs, and escape \n, \t
// from the value.
func escapeControlCharsFromPayload(by []byte) []byte {
// we'll search for `"text": "..."` or `"fallback": "..."`, ...
keys := "text|fallback|pretext|author_name|title|value"
// the regexp reads like this:
// (?s): this flag let . match \n (default is false)
// "(keys)": we search for the keys defined above
// \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs
// ": a double-quote
// (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote
// ": a double-quote
r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"`
re := regexp.MustCompile(r)
// the function that will escape \n and \t on the regexp matches
repl := func(b []byte) []byte {
if bytes.Contains(b, []byte("\n")) {
b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1)
}
if bytes.Contains(b, []byte("\t")) {
b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1)
}
return b
}
return re.ReplaceAllFunc(by, repl)
}
func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) {
decoder := json.NewDecoder(bytes.NewReader(by))
var o IncomingWebhookRequest
err := decoder.Decode(&o)
if err == nil {
return &o, nil
} else {
return nil, err
}
}
func IncomingWebhookRequestFromJson(data io.Reader) (*IncomingWebhookRequest, *AppError) {
buf := new(bytes.Buffer)
buf.ReadFrom(data)
by := buf.Bytes()
// Try to decode the JSON data. Only if it fails, try to escape control
// characters from the strings contained in the JSON data.
o, err := decodeIncomingWebhookRequest(by)
if err != nil {
o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by))
if err != nil {
return nil, NewAppError("IncomingWebhookRequestFromJson", "model.incoming_hook.parse_data.app_error", nil, err.Error(), http.StatusBadRequest)
}
}
o.Attachments = StringifySlackFieldValue(o.Attachments)
return o, nil
}
func (o *IncomingWebhookRequest) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type InitialLoad struct {
User *User `json:"user"`
TeamMembers []*TeamMember `json:"team_members"`
Teams []*Team `json:"teams"`
Preferences Preferences `json:"preferences"`
ClientCfg map[string]string `json:"client_cfg"`
LicenseCfg map[string]string `json:"license_cfg"`
NoAccounts bool `json:"no_accounts"`
}
func (me *InitialLoad) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func InitialLoadFromJson(data io.Reader) *InitialLoad {
var o *InitialLoad
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,525 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"strconv"
"strings"
)
const (
POST_ACTION_TYPE_BUTTON = "button"
POST_ACTION_TYPE_SELECT = "select"
INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS = 3000
)
var PostActionRetainPropKeys = []string{"from_webhook", "override_username", "override_icon_url"}
type DoPostActionRequest struct {
SelectedOption string `json:"selected_option,omitempty"`
Cookie string `json:"cookie,omitempty"`
}
type PostAction struct {
// A unique Action ID. If not set, generated automatically.
Id string `json:"id,omitempty"`
// The type of the interactive element. Currently supported are
// "select" and "button".
Type string `json:"type,omitempty"`
// The text on the button, or in the select placeholder.
Name string `json:"name,omitempty"`
// If the action is disabled.
Disabled bool `json:"disabled,omitempty"`
// Style defines a text and border style.
// Supported values are "default", "primary", "success", "good", "warning", "danger"
// and any hex color.
Style string `json:"style,omitempty"`
// DataSource indicates the data source for the select action. If left
// empty, the select is populated from Options. Other supported values
// are "users" and "channels".
DataSource string `json:"data_source,omitempty"`
// Options contains the values listed in a select dropdown on the post.
Options []*PostActionOptions `json:"options,omitempty"`
// DefaultOption contains the option, if any, that will appear as the
// default selection in a select box. It has no effect when used with
// other types of actions.
DefaultOption string `json:"default_option,omitempty"`
// Defines the interaction with the backend upon a user action.
// Integration contains Context, which is private plugin data;
// Integrations are stripped from Posts when they are sent to the
// client, or are encrypted in a Cookie.
Integration *PostActionIntegration `json:"integration,omitempty"`
Cookie string `json:"cookie,omitempty" db:"-"`
}
func (p *PostAction) Equals(input *PostAction) bool {
if p.Id != input.Id {
return false
}
if p.Type != input.Type {
return false
}
if p.Name != input.Name {
return false
}
if p.DataSource != input.DataSource {
return false
}
if p.DefaultOption != input.DefaultOption {
return false
}
if p.Cookie != input.Cookie {
return false
}
// Compare PostActionOptions
if len(p.Options) != len(input.Options) {
return false
}
for k := range p.Options {
if p.Options[k].Text != input.Options[k].Text {
return false
}
if p.Options[k].Value != input.Options[k].Value {
return false
}
}
// Compare PostActionIntegration
if p.Integration.URL != input.Integration.URL {
return false
}
if len(p.Integration.Context) != len(input.Integration.Context) {
return false
}
for key, value := range p.Integration.Context {
inputValue, ok := input.Integration.Context[key]
if !ok {
return false
}
if value != inputValue {
return false
}
}
return true
}
// PostActionCookie is set by the server, serialized and encrypted into
// PostAction.Cookie. The clients should hold on to it, and include it with
// subsequent DoPostAction requests. This allows the server to access the
// action metadata even when it's not available in the database, for ephemeral
// posts.
type PostActionCookie struct {
Type string `json:"type,omitempty"`
PostId string `json:"post_id,omitempty"`
RootPostId string `json:"root_post_id,omitempty"`
ChannelId string `json:"channel_id,omitempty"`
DataSource string `json:"data_source,omitempty"`
Integration *PostActionIntegration `json:"integration,omitempty"`
RetainProps map[string]interface{} `json:"retain_props,omitempty"`
RemoveProps []string `json:"remove_props,omitempty"`
}
type PostActionOptions struct {
Text string `json:"text"`
Value string `json:"value"`
}
type PostActionIntegration struct {
URL string `json:"url,omitempty"`
Context map[string]interface{} `json:"context,omitempty"`
}
type PostActionIntegrationRequest struct {
UserId string `json:"user_id"`
UserName string `json:"user_name"`
ChannelId string `json:"channel_id"`
ChannelName string `json:"channel_name"`
TeamId string `json:"team_id"`
TeamName string `json:"team_domain"`
PostId string `json:"post_id"`
TriggerId string `json:"trigger_id"`
Type string `json:"type"`
DataSource string `json:"data_source"`
Context map[string]interface{} `json:"context,omitempty"`
}
type PostActionIntegrationResponse struct {
Update *Post `json:"update"`
EphemeralText string `json:"ephemeral_text"`
SkipSlackParsing bool `json:"skip_slack_parsing"` // Set to `true` to skip the Slack-compatibility handling of Text.
}
type PostActionAPIResponse struct {
Status string `json:"status"` // needed to maintain backwards compatibility
TriggerId string `json:"trigger_id"`
}
type Dialog struct {
CallbackId string `json:"callback_id"`
Title string `json:"title"`
IntroductionText string `json:"introduction_text"`
IconURL string `json:"icon_url"`
Elements []DialogElement `json:"elements"`
SubmitLabel string `json:"submit_label"`
NotifyOnCancel bool `json:"notify_on_cancel"`
State string `json:"state"`
}
type DialogElement struct {
DisplayName string `json:"display_name"`
Name string `json:"name"`
Type string `json:"type"`
SubType string `json:"subtype"`
Default string `json:"default"`
Placeholder string `json:"placeholder"`
HelpText string `json:"help_text"`
Optional bool `json:"optional"`
MinLength int `json:"min_length"`
MaxLength int `json:"max_length"`
DataSource string `json:"data_source"`
Options []*PostActionOptions `json:"options"`
}
type OpenDialogRequest struct {
TriggerId string `json:"trigger_id"`
URL string `json:"url"`
Dialog Dialog `json:"dialog"`
}
type SubmitDialogRequest struct {
Type string `json:"type"`
URL string `json:"url,omitempty"`
CallbackId string `json:"callback_id"`
State string `json:"state"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"`
Submission map[string]interface{} `json:"submission"`
Cancelled bool `json:"cancelled"`
}
type SubmitDialogResponse struct {
Error string `json:"error,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}
func GenerateTriggerId(userId string, s crypto.Signer) (string, string, *AppError) {
clientTriggerId := NewId()
triggerData := strings.Join([]string{clientTriggerId, userId, strconv.FormatInt(GetMillis(), 10)}, ":") + ":"
h := crypto.SHA256
sum := h.New()
sum.Write([]byte(triggerData))
signature, err := s.Sign(rand.Reader, sum.Sum(nil), h)
if err != nil {
return "", "", NewAppError("GenerateTriggerId", "interactive_message.generate_trigger_id.signing_failed", nil, err.Error(), http.StatusInternalServerError)
}
base64Sig := base64.StdEncoding.EncodeToString(signature)
triggerId := base64.StdEncoding.EncodeToString([]byte(triggerData + base64Sig))
return clientTriggerId, triggerId, nil
}
func (r *PostActionIntegrationRequest) GenerateTriggerId(s crypto.Signer) (string, string, *AppError) {
clientTriggerId, triggerId, err := GenerateTriggerId(r.UserId, s)
if err != nil {
return "", "", err
}
r.TriggerId = triggerId
return clientTriggerId, triggerId, nil
}
func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, string, *AppError) {
triggerIdBytes, err := base64.StdEncoding.DecodeString(triggerId)
if err != nil {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed", nil, err.Error(), http.StatusBadRequest)
}
split := strings.Split(string(triggerIdBytes), ":")
if len(split) != 4 {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.missing_data", nil, "", http.StatusBadRequest)
}
clientTriggerId := split[0]
userId := split[1]
timestampStr := split[2]
timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
now := GetMillis()
if now-timestamp > INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.expired", map[string]interface{}{"Seconds": INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS / 1000}, "", http.StatusBadRequest)
}
signature, err := base64.StdEncoding.DecodeString(split[3])
if err != nil {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed_signature", nil, err.Error(), http.StatusBadRequest)
}
var esig struct {
R, S *big.Int
}
if _, err := asn1.Unmarshal(signature, &esig); err != nil {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.signature_decode_failed", nil, err.Error(), http.StatusBadRequest)
}
triggerData := strings.Join([]string{clientTriggerId, userId, timestampStr}, ":") + ":"
h := crypto.SHA256
sum := h.New()
sum.Write([]byte(triggerData))
if !ecdsa.Verify(&s.PublicKey, sum.Sum(nil), esig.R, esig.S) {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.verify_signature_failed", nil, "", http.StatusBadRequest)
}
return clientTriggerId, userId, nil
}
func (r *OpenDialogRequest) DecodeAndVerifyTriggerId(s *ecdsa.PrivateKey) (string, string, *AppError) {
return DecodeAndVerifyTriggerId(r.TriggerId, s)
}
func (r *PostActionIntegrationRequest) ToJson() []byte {
b, _ := json.Marshal(r)
return b
}
func PostActionIntegrationRequestFromJson(data io.Reader) *PostActionIntegrationRequest {
var o *PostActionIntegrationRequest
err := json.NewDecoder(data).Decode(&o)
if err != nil {
return nil
}
return o
}
func (r *PostActionIntegrationResponse) ToJson() []byte {
b, _ := json.Marshal(r)
return b
}
func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse {
var o *PostActionIntegrationResponse
err := json.NewDecoder(data).Decode(&o)
if err != nil {
return nil
}
return o
}
func SubmitDialogRequestFromJson(data io.Reader) *SubmitDialogRequest {
var o *SubmitDialogRequest
err := json.NewDecoder(data).Decode(&o)
if err != nil {
return nil
}
return o
}
func (r *SubmitDialogRequest) ToJson() []byte {
b, _ := json.Marshal(r)
return b
}
func SubmitDialogResponseFromJson(data io.Reader) *SubmitDialogResponse {
var o *SubmitDialogResponse
err := json.NewDecoder(data).Decode(&o)
if err != nil {
return nil
}
return o
}
func (r *SubmitDialogResponse) ToJson() []byte {
b, _ := json.Marshal(r)
return b
}
func (o *Post) StripActionIntegrations() {
attachments := o.Attachments()
if o.GetProp("attachments") != nil {
o.AddProp("attachments", attachments)
}
for _, attachment := range attachments {
for _, action := range attachment.Actions {
action.Integration = nil
}
}
}
func (o *Post) GetAction(id string) *PostAction {
for _, attachment := range o.Attachments() {
for _, action := range attachment.Actions {
if action.Id == id {
return action
}
}
}
return nil
}
func (o *Post) GenerateActionIds() {
if o.GetProp("attachments") != nil {
o.AddProp("attachments", o.Attachments())
}
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
for _, attachment := range attachments {
for _, action := range attachment.Actions {
if action.Id == "" {
action.Id = NewId()
}
}
}
}
}
func AddPostActionCookies(o *Post, secret []byte) *Post {
p := o.Clone()
// retainedProps carry over their value from the old post, including no value
retainProps := map[string]interface{}{}
removeProps := []string{}
for _, key := range PostActionRetainPropKeys {
value, ok := p.GetProps()[key]
if ok {
retainProps[key] = value
} else {
removeProps = append(removeProps, key)
}
}
attachments := p.Attachments()
for _, attachment := range attachments {
for _, action := range attachment.Actions {
c := &PostActionCookie{
Type: action.Type,
ChannelId: p.ChannelId,
DataSource: action.DataSource,
Integration: action.Integration,
RetainProps: retainProps,
RemoveProps: removeProps,
}
c.PostId = p.Id
if p.RootId == "" {
c.RootPostId = p.Id
} else {
c.RootPostId = p.RootId
}
b, _ := json.Marshal(c)
action.Cookie, _ = encryptPostActionCookie(string(b), secret)
}
}
return p
}
func encryptPostActionCookie(plain string, secret []byte) (string, error) {
if len(secret) == 0 {
return plain, nil
}
block, err := aes.NewCipher(secret)
if err != nil {
return "", err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, aesgcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return "", err
}
sealed := aesgcm.Seal(nil, nonce, []byte(plain), nil)
combined := append(nonce, sealed...)
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(combined)))
base64.StdEncoding.Encode(encoded, combined)
return string(encoded), nil
}
func DecryptPostActionCookie(encoded string, secret []byte) (string, error) {
if len(secret) == 0 {
return encoded, nil
}
block, err := aes.NewCipher(secret)
if err != nil {
return "", err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(encoded)))
n, err := base64.StdEncoding.Decode(decoded, []byte(encoded))
if err != nil {
return "", err
}
decoded = decoded[:n]
nonceSize := aesgcm.NonceSize()
if len(decoded) < nonceSize {
return "", fmt.Errorf("cookie too short")
}
nonce, decoded := decoded[:nonceSize], decoded[nonceSize:]
plain, err := aesgcm.Open(nil, nonce, decoded, nil)
if err != nil {
return "", err
}
return string(plain), nil
}
func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest {
var o *DoPostActionRequest
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,125 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"time"
)
const (
JOB_TYPE_DATA_RETENTION = "data_retention"
JOB_TYPE_MESSAGE_EXPORT = "message_export"
JOB_TYPE_ELASTICSEARCH_POST_INDEXING = "elasticsearch_post_indexing"
JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION = "elasticsearch_post_aggregation"
JOB_TYPE_BLEVE_POST_INDEXING = "bleve_post_indexing"
JOB_TYPE_LDAP_SYNC = "ldap_sync"
JOB_TYPE_MIGRATIONS = "migrations"
JOB_TYPE_PLUGINS = "plugins"
JOB_STATUS_PENDING = "pending"
JOB_STATUS_IN_PROGRESS = "in_progress"
JOB_STATUS_SUCCESS = "success"
JOB_STATUS_ERROR = "error"
JOB_STATUS_CANCEL_REQUESTED = "cancel_requested"
JOB_STATUS_CANCELED = "canceled"
JOB_STATUS_WARNING = "warning"
)
type Job struct {
Id string `json:"id"`
Type string `json:"type"`
Priority int64 `json:"priority"`
CreateAt int64 `json:"create_at"`
StartAt int64 `json:"start_at"`
LastActivityAt int64 `json:"last_activity_at"`
Status string `json:"status"`
Progress int64 `json:"progress"`
Data map[string]string `json:"data"`
}
func (j *Job) IsValid() *AppError {
if !IsValidId(j.Id) {
return NewAppError("Job.IsValid", "model.job.is_valid.id.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
if j.CreateAt == 0 {
return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Type {
case JOB_TYPE_DATA_RETENTION:
case JOB_TYPE_ELASTICSEARCH_POST_INDEXING:
case JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION:
case JOB_TYPE_BLEVE_POST_INDEXING:
case JOB_TYPE_LDAP_SYNC:
case JOB_TYPE_MESSAGE_EXPORT:
case JOB_TYPE_MIGRATIONS:
case JOB_TYPE_PLUGINS:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Status {
case JOB_STATUS_PENDING:
case JOB_STATUS_IN_PROGRESS:
case JOB_STATUS_SUCCESS:
case JOB_STATUS_ERROR:
case JOB_STATUS_CANCEL_REQUESTED:
case JOB_STATUS_CANCELED:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
return nil
}
func (j *Job) ToJson() string {
b, _ := json.Marshal(j)
return string(b)
}
func JobFromJson(data io.Reader) *Job {
var job Job
if err := json.NewDecoder(data).Decode(&job); err == nil {
return &job
} else {
return nil
}
}
func JobsToJson(jobs []*Job) string {
b, _ := json.Marshal(jobs)
return string(b)
}
func JobsFromJson(data io.Reader) []*Job {
var jobs []*Job
if err := json.NewDecoder(data).Decode(&jobs); err == nil {
return jobs
} else {
return nil
}
}
func (j *Job) DataToJson() string {
b, _ := json.Marshal(j.Data)
return string(b)
}
type Worker interface {
Run()
Stop()
JobChannel() chan<- Job
}
type Scheduler interface {
Name() string
JobType() string
Enabled(cfg *Config) bool
NextScheduleTime(cfg *Config, now time.Time, pendingJobs bool, lastSuccessfulJob *Job) *time.Time
ScheduleJob(cfg *Config, pendingJobs bool, lastSuccessfulJob *Job) (*Job, *AppError)
}

View File

@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
const (
USER_AUTH_SERVICE_LDAP = "ldap"
)

View File

@ -0,0 +1,278 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
const (
EXPIRED_LICENSE_ERROR = "api.license.add_license.expired.app_error"
INVALID_LICENSE_ERROR = "api.license.add_license.invalid.app_error"
LICENSE_GRACE_PERIOD = 1000 * 60 * 60 * 24 * 10 //10 days
LICENSE_RENEWAL_LINK = "https://licensing.mattermost.com/renew"
)
type LicenseRecord struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
Bytes string `json:"-"`
}
type License struct {
Id string `json:"id"`
IssuedAt int64 `json:"issued_at"`
StartsAt int64 `json:"starts_at"`
ExpiresAt int64 `json:"expires_at"`
Customer *Customer `json:"customer"`
Features *Features `json:"features"`
SkuName string `json:"sku_name"`
SkuShortName string `json:"sku_short_name"`
}
type Customer struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Company string `json:"company"`
}
type TrialLicenseRequest struct {
ServerID string `json:"server_id"`
Email string `json:"email"`
Name string `json:"name"`
SiteURL string `json:"site_url"`
SiteName string `json:"site_name"`
Users int `json:"users"`
TermsAccepted bool `json:"terms_accepted"`
ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
}
func (tlr *TrialLicenseRequest) ToJson() string {
b, _ := json.Marshal(tlr)
return string(b)
}
type Features struct {
Users *int `json:"users"`
LDAP *bool `json:"ldap"`
LDAPGroups *bool `json:"ldap_groups"`
MFA *bool `json:"mfa"`
GoogleOAuth *bool `json:"google_oauth"`
Office365OAuth *bool `json:"office365_oauth"`
Compliance *bool `json:"compliance"`
Cluster *bool `json:"cluster"`
Metrics *bool `json:"metrics"`
MHPNS *bool `json:"mhpns"`
SAML *bool `json:"saml"`
Elasticsearch *bool `json:"elastic_search"`
Announcement *bool `json:"announcement"`
ThemeManagement *bool `json:"theme_management"`
EmailNotificationContents *bool `json:"email_notification_contents"`
DataRetention *bool `json:"data_retention"`
MessageExport *bool `json:"message_export"`
CustomPermissionsSchemes *bool `json:"custom_permissions_schemes"`
CustomTermsOfService *bool `json:"custom_terms_of_service"`
GuestAccounts *bool `json:"guest_accounts"`
GuestAccountsPermissions *bool `json:"guest_accounts_permissions"`
IDLoadedPushNotifications *bool `json:"id_loaded"`
LockTeammateNameDisplay *bool `json:"lock_teammate_name_display"`
EnterprisePlugins *bool `json:"enterprise_plugins"`
// after we enabled more features we'll need to control them with this
FutureFeatures *bool `json:"future_features"`
}
func (f *Features) ToMap() map[string]interface{} {
return map[string]interface{}{
"ldap": *f.LDAP,
"ldap_groups": *f.LDAPGroups,
"mfa": *f.MFA,
"google": *f.GoogleOAuth,
"office365": *f.Office365OAuth,
"compliance": *f.Compliance,
"cluster": *f.Cluster,
"metrics": *f.Metrics,
"mhpns": *f.MHPNS,
"saml": *f.SAML,
"elastic_search": *f.Elasticsearch,
"email_notification_contents": *f.EmailNotificationContents,
"data_retention": *f.DataRetention,
"message_export": *f.MessageExport,
"custom_permissions_schemes": *f.CustomPermissionsSchemes,
"guest_accounts": *f.GuestAccounts,
"guest_accounts_permissions": *f.GuestAccountsPermissions,
"id_loaded": *f.IDLoadedPushNotifications,
"lock_teammate_name_display": *f.LockTeammateNameDisplay,
"enterprise_plugins": *f.EnterprisePlugins,
"future": *f.FutureFeatures,
}
}
func (f *Features) SetDefaults() {
if f.FutureFeatures == nil {
f.FutureFeatures = NewBool(true)
}
if f.Users == nil {
f.Users = NewInt(0)
}
if f.LDAP == nil {
f.LDAP = NewBool(*f.FutureFeatures)
}
if f.LDAPGroups == nil {
f.LDAPGroups = NewBool(*f.FutureFeatures)
}
if f.MFA == nil {
f.MFA = NewBool(*f.FutureFeatures)
}
if f.GoogleOAuth == nil {
f.GoogleOAuth = NewBool(*f.FutureFeatures)
}
if f.Office365OAuth == nil {
f.Office365OAuth = NewBool(*f.FutureFeatures)
}
if f.Compliance == nil {
f.Compliance = NewBool(*f.FutureFeatures)
}
if f.Cluster == nil {
f.Cluster = NewBool(*f.FutureFeatures)
}
if f.Metrics == nil {
f.Metrics = NewBool(*f.FutureFeatures)
}
if f.MHPNS == nil {
f.MHPNS = NewBool(*f.FutureFeatures)
}
if f.SAML == nil {
f.SAML = NewBool(*f.FutureFeatures)
}
if f.Elasticsearch == nil {
f.Elasticsearch = NewBool(*f.FutureFeatures)
}
if f.Announcement == nil {
f.Announcement = NewBool(true)
}
if f.ThemeManagement == nil {
f.ThemeManagement = NewBool(true)
}
if f.EmailNotificationContents == nil {
f.EmailNotificationContents = NewBool(*f.FutureFeatures)
}
if f.DataRetention == nil {
f.DataRetention = NewBool(*f.FutureFeatures)
}
if f.MessageExport == nil {
f.MessageExport = NewBool(*f.FutureFeatures)
}
if f.CustomPermissionsSchemes == nil {
f.CustomPermissionsSchemes = NewBool(*f.FutureFeatures)
}
if f.GuestAccounts == nil {
f.GuestAccounts = NewBool(*f.FutureFeatures)
}
if f.GuestAccountsPermissions == nil {
f.GuestAccountsPermissions = NewBool(*f.FutureFeatures)
}
if f.CustomTermsOfService == nil {
f.CustomTermsOfService = NewBool(*f.FutureFeatures)
}
if f.IDLoadedPushNotifications == nil {
f.IDLoadedPushNotifications = NewBool(*f.FutureFeatures)
}
if f.LockTeammateNameDisplay == nil {
f.LockTeammateNameDisplay = NewBool(*f.FutureFeatures)
}
if f.EnterprisePlugins == nil {
f.EnterprisePlugins = NewBool(*f.FutureFeatures)
}
}
func (l *License) IsExpired() bool {
return l.ExpiresAt < GetMillis()
}
func (l *License) IsPastGracePeriod() bool {
timeDiff := GetMillis() - l.ExpiresAt
return timeDiff > LICENSE_GRACE_PERIOD
}
func (l *License) IsStarted() bool {
return l.StartsAt < GetMillis()
}
func (l *License) ToJson() string {
b, _ := json.Marshal(l)
return string(b)
}
// NewTestLicense returns a license that expires in the future and has the given features.
func NewTestLicense(features ...string) *License {
ret := &License{
ExpiresAt: GetMillis() + 90*24*60*60*1000,
Customer: &Customer{},
Features: &Features{},
}
ret.Features.SetDefaults()
featureMap := map[string]bool{}
for _, feature := range features {
featureMap[feature] = true
}
featureJson, _ := json.Marshal(featureMap)
json.Unmarshal(featureJson, &ret.Features)
return ret
}
func LicenseFromJson(data io.Reader) *License {
var o *License
json.NewDecoder(data).Decode(&o)
return o
}
func (lr *LicenseRecord) IsValid() *AppError {
if !IsValidId(lr.Id) {
return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if lr.CreateAt == 0 {
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 {
return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (lr *LicenseRecord) PreSave() {
lr.CreateAt = GetMillis()
}

View File

@ -0,0 +1,193 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/binary"
"encoding/json"
"fmt"
"hash/fnv"
"net/http"
"time"
"unicode/utf8"
"github.com/dyatlov/go-opengraph/opengraph"
)
const (
LINK_METADATA_TYPE_IMAGE LinkMetadataType = "image"
LINK_METADATA_TYPE_NONE LinkMetadataType = "none"
LINK_METADATA_TYPE_OPENGRAPH LinkMetadataType = "opengraph"
MAX_IMAGES int = 5
)
type LinkMetadataType string
// LinkMetadata stores arbitrary data about a link posted in a message. This includes dimensions of linked images
// and OpenGraph metadata.
type LinkMetadata struct {
// Hash is a value computed from the URL and Timestamp for use as a primary key in the database.
Hash int64
URL string
Timestamp int64
Type LinkMetadataType
// Data is the actual metadata for the link. It should contain data of one of the following types:
// - *model.PostImage if the linked content is an image
// - *opengraph.OpenGraph if the linked content is an HTML document
// - nil if the linked content has no metadata
Data interface{}
}
// truncateText ensure string is 300 chars, truncate and add ellipsis
// if it was bigger.
func truncateText(original string) string {
if utf8.RuneCountInString(original) > 300 {
return fmt.Sprintf("%.300s[...]", original)
}
return original
}
func firstNImages(images []*opengraph.Image, maxImages int) []*opengraph.Image {
if maxImages < 0 { // dont break stuff, if it's weird, go for sane defaults
maxImages = MAX_IMAGES
}
numImages := len(images)
if numImages > maxImages {
return images[0:maxImages]
}
return images
}
// TruncateOpenGraph ensure OpenGraph metadata doesn't grow too big by
// shortening strings, trimming fields and reducing the number of
// images.
func TruncateOpenGraph(ogdata *opengraph.OpenGraph) *opengraph.OpenGraph {
if ogdata != nil {
empty := &opengraph.OpenGraph{}
ogdata.Title = truncateText(ogdata.Title)
ogdata.Description = truncateText(ogdata.Description)
ogdata.SiteName = truncateText(ogdata.SiteName)
ogdata.Article = empty.Article
ogdata.Book = empty.Book
ogdata.Profile = empty.Profile
ogdata.Determiner = empty.Determiner
ogdata.Locale = empty.Locale
ogdata.LocalesAlternate = empty.LocalesAlternate
ogdata.Images = firstNImages(ogdata.Images, MAX_IMAGES)
ogdata.Audios = empty.Audios
ogdata.Videos = empty.Videos
}
return ogdata
}
func (o *LinkMetadata) PreSave() {
o.Hash = GenerateLinkMetadataHash(o.URL, o.Timestamp)
}
func (o *LinkMetadata) IsValid() *AppError {
if o.URL == "" {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.url.app_error", nil, "", http.StatusBadRequest)
}
if o.Timestamp == 0 || !isRoundedToNearestHour(o.Timestamp) {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.timestamp.app_error", nil, "", http.StatusBadRequest)
}
switch o.Type {
case LINK_METADATA_TYPE_IMAGE:
if o.Data == nil {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data.app_error", nil, "", http.StatusBadRequest)
}
if _, ok := o.Data.(*PostImage); !ok {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data_type.app_error", nil, "", http.StatusBadRequest)
}
case LINK_METADATA_TYPE_NONE:
if o.Data != nil {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data_type.app_error", nil, "", http.StatusBadRequest)
}
case LINK_METADATA_TYPE_OPENGRAPH:
if o.Data == nil {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data.app_error", nil, "", http.StatusBadRequest)
}
if _, ok := o.Data.(*opengraph.OpenGraph); !ok {
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data_type.app_error", nil, "", http.StatusBadRequest)
}
default:
return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.type.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
// DeserializeDataToConcreteType converts o.Data from JSON into properly structured data. This is intended to be used
// after getting a LinkMetadata object that has been stored in the database.
func (o *LinkMetadata) DeserializeDataToConcreteType() error {
var b []byte
switch t := o.Data.(type) {
case []byte:
// MySQL uses a byte slice for JSON
b = t
case string:
// Postgres uses a string for JSON
b = []byte(t)
}
if b == nil {
// Data doesn't need to be fixed
return nil
}
var data interface{}
var err error
switch o.Type {
case LINK_METADATA_TYPE_IMAGE:
image := &PostImage{}
err = json.Unmarshal(b, &image)
data = image
case LINK_METADATA_TYPE_OPENGRAPH:
og := &opengraph.OpenGraph{}
json.Unmarshal(b, &og)
data = og
}
if err != nil {
return err
}
o.Data = data
return nil
}
// FloorToNearestHour takes a timestamp (in milliseconds) and returns it rounded to the previous hour in UTC.
func FloorToNearestHour(ms int64) int64 {
t := time.Unix(0, ms*int64(1000*1000))
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).UnixNano() / int64(time.Millisecond)
}
// isRoundedToNearestHour returns true if the given timestamp (in milliseconds) has been rounded to the nearest hour in UTC.
func isRoundedToNearestHour(ms int64) bool {
return FloorToNearestHour(ms) == ms
}
// GenerateLinkMetadataHash generates a unique hash for a given URL and timestamp for use as a database key.
func GenerateLinkMetadataHash(url string, timestamp int64) int64 {
hash := fnv.New32()
// Note that we ignore write errors here because the Hash interface says that its Write will never return an error
binary.Write(hash, binary.LittleEndian, timestamp)
hash.Write([]byte(url))
return int64(hash.Sum32())
}

View File

@ -0,0 +1,486 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/blang/semver"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
)
type PluginOption struct {
// The display name for the option.
DisplayName string `json:"display_name" yaml:"display_name"`
// The string value for the option.
Value string `json:"value" yaml:"value"`
}
type PluginSettingType int
const (
Bool PluginSettingType = iota
Dropdown
Generated
Radio
Text
LongText
Number
Username
Custom
)
type PluginSetting struct {
// The key that the setting will be assigned to in the configuration file.
Key string `json:"key" yaml:"key"`
// The display name for the setting.
DisplayName string `json:"display_name" yaml:"display_name"`
// The type of the setting.
//
// "bool" will result in a boolean true or false setting.
//
// "dropdown" will result in a string setting that allows the user to select from a list of
// pre-defined options.
//
// "generated" will result in a string setting that is set to a random, cryptographically secure
// string.
//
// "radio" will result in a string setting that allows the user to select from a short selection
// of pre-defined options.
//
// "text" will result in a string setting that can be typed in manually.
//
// "longtext" will result in a multi line string that can be typed in manually.
//
// "number" will result in in integer setting that can be typed in manually.
//
// "username" will result in a text setting that will autocomplete to a username.
//
// "custom" will result in a custom defined setting and will load the custom component registered for the Web App System Console.
Type string `json:"type" yaml:"type"`
// The help text to display to the user. Supports Markdown formatting.
HelpText string `json:"help_text" yaml:"help_text"`
// The help text to display alongside the "Regenerate" button for settings of the "generated" type.
RegenerateHelpText string `json:"regenerate_help_text,omitempty" yaml:"regenerate_help_text,omitempty"`
// The placeholder to display for "generated", "text", "longtext", "number" and "username" types when blank.
Placeholder string `json:"placeholder" yaml:"placeholder"`
// The default value of the setting.
Default interface{} `json:"default" yaml:"default"`
// For "radio" or "dropdown" settings, this is the list of pre-defined options that the user can choose
// from.
Options []*PluginOption `json:"options,omitempty" yaml:"options,omitempty"`
}
type PluginSettingsSchema struct {
// Optional text to display above the settings. Supports Markdown formatting.
Header string `json:"header" yaml:"header"`
// Optional text to display below the settings. Supports Markdown formatting.
Footer string `json:"footer" yaml:"footer"`
// A list of setting definitions.
Settings []*PluginSetting `json:"settings" yaml:"settings"`
}
// The plugin manifest defines the metadata required to load and present your plugin. The manifest
// file should be named plugin.json or plugin.yaml and placed in the top of your
// plugin bundle.
//
// Example plugin.json:
//
//
// {
// "id": "com.mycompany.myplugin",
// "name": "My Plugin",
// "description": "This is my plugin",
// "homepage_url": "https://example.com",
// "support_url": "https://example.com/support",
// "release_notes_url": "https://example.com/releases/v0.0.1",
// "icon_path": "assets/logo.svg",
// "version": "0.1.0",
// "min_server_version": "5.6.0",
// "server": {
// "executables": {
// "linux-amd64": "server/dist/plugin-linux-amd64",
// "darwin-amd64": "server/dist/plugin-darwin-amd64",
// "windows-amd64": "server/dist/plugin-windows-amd64.exe"
// }
// },
// "webapp": {
// "bundle_path": "webapp/dist/main.js"
// },
// "settings_schema": {
// "header": "Some header text",
// "footer": "Some footer text",
// "settings": [{
// "key": "someKey",
// "display_name": "Enable Extra Feature",
// "type": "bool",
// "help_text": "When true, an extra feature will be enabled!",
// "default": "false"
// }]
// },
// "props": {
// "someKey": "someData"
// }
// }
type Manifest struct {
// The id is a globally unique identifier that represents your plugin. Ids must be at least
// 3 characters, at most 190 characters and must match ^[a-zA-Z0-9-_\.]+$.
// Reverse-DNS notation using a name you control is a good option, e.g. "com.mycompany.myplugin".
Id string `json:"id" yaml:"id"`
// The name to be displayed for the plugin.
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// A description of what your plugin is and does.
Description string `json:"description,omitempty" yaml:"description,omitempty"`
// HomepageURL is an optional link to learn more about the plugin.
HomepageURL string `json:"homepage_url,omitempty" yaml:"homepage_url,omitempty"`
// SupportURL is an optional URL where plugin issues can be reported.
SupportURL string `json:"support_url,omitempty" yaml:"support_url,omitempty"`
// ReleaseNotesURL is an optional URL where a changelog for the release can be found.
ReleaseNotesURL string `json:"release_notes_url,omitempty" yaml:"release_notes_url,omitempty"`
// A relative file path in the bundle that points to the plugins svg icon for use with the Plugin Marketplace.
// This should be relative to the root of your bundle and the location of the manifest file. Bitmap image formats are not supported.
IconPath string `json:"icon_path,omitempty" yaml:"icon_path,omitempty"`
// A version number for your plugin. Semantic versioning is recommended: http://semver.org
Version string `json:"version" yaml:"version"`
// The minimum Mattermost server version required for your plugin.
//
// Minimum server version: 5.6
MinServerVersion string `json:"min_server_version,omitempty" yaml:"min_server_version,omitempty"`
// Server defines the server-side portion of your plugin.
Server *ManifestServer `json:"server,omitempty" yaml:"server,omitempty"`
// Backend is a deprecated flag for defining the server-side portion of your plugin. Going forward, use Server instead.
Backend *ManifestServer `json:"backend,omitempty" yaml:"backend,omitempty"`
// If your plugin extends the web app, you'll need to define webapp.
Webapp *ManifestWebapp `json:"webapp,omitempty" yaml:"webapp,omitempty"`
// To allow administrators to configure your plugin via the Mattermost system console, you can
// provide your settings schema.
SettingsSchema *PluginSettingsSchema `json:"settings_schema,omitempty" yaml:"settings_schema,omitempty"`
// Plugins can store any kind of data in Props to allow other plugins to use it.
Props map[string]interface{} `json:"props,omitempty" yaml:"props,omitempty"`
// RequiredConfig defines any required server configuration fields for the plugin to function properly.
//
// Use the plugin helpers CheckRequiredServerConfiguration method to enforce this.
RequiredConfig *Config `json:"required_configuration,omitempty" yaml:"required_configuration,omitempty"`
}
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"`
// 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.
//
// On Windows, this file must have a ".exe" extension.
//
// If your plugin is compiled for multiple platforms, consider bundling them together
// and using the Executables field instead.
Executable string `json:"executable" yaml:"executable"`
}
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"`
// DarwinAmd64 is the path to your executable binary for the corresponding platform
DarwinAmd64 string `json:"darwin-amd64,omitempty" yaml:"darwin-amd64,omitempty"`
// WindowsAmd64 is the path to your executable binary for the corresponding platform
// This file must have a ".exe" extension
WindowsAmd64 string `json:"windows-amd64,omitempty" yaml:"windows-amd64,omitempty"`
}
type ManifestWebapp struct {
// The path to your webapp bundle. This should be relative to the root of your bundle and the
// location of the manifest file.
BundlePath string `json:"bundle_path" yaml:"bundle_path"`
// BundleHash is the 64-bit FNV-1a hash of the webapp bundle, computed when the plugin is loaded
BundleHash []byte `json:"-"`
}
func (m *Manifest) ToJson() string {
b, _ := json.Marshal(m)
return string(b)
}
func ManifestListToJson(m []*Manifest) string {
b, _ := json.Marshal(m)
return string(b)
}
func ManifestFromJson(data io.Reader) *Manifest {
var m *Manifest
json.NewDecoder(data).Decode(&m)
return m
}
func ManifestListFromJson(data io.Reader) []*Manifest {
var manifests []*Manifest
json.NewDecoder(data).Decode(&manifests)
return manifests
}
func (m *Manifest) HasClient() bool {
return m.Webapp != nil
}
func (m *Manifest) ClientManifest() *Manifest {
cm := new(Manifest)
*cm = *m
cm.Name = ""
cm.Description = ""
cm.Server = nil
if cm.Webapp != nil {
cm.Webapp = new(ManifestWebapp)
*cm.Webapp = *m.Webapp
cm.Webapp.BundlePath = "/static/" + m.Id + "/" + fmt.Sprintf("%s_%x_bundle.js", m.Id, m.Webapp.BundleHash)
}
return cm
}
// GetExecutableForRuntime returns the path to the executable for the given runtime architecture.
//
// If the manifest defines multiple executables, but none match, or if only a single executable
// is defined, the Executable field will be returned. This method does not guarantee that the
// resulting binary can actually execute on the given platform.
func (m *Manifest) GetExecutableForRuntime(goOs, goArch string) string {
server := m.Server
// Support the deprecated backend parameter.
if server == nil {
server = m.Backend
}
if server == nil {
return ""
}
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 executable == "" {
executable = server.Executable
}
return executable
}
func (m *Manifest) HasServer() bool {
return m.Server != nil || m.Backend != nil
}
func (m *Manifest) HasWebapp() bool {
return m.Webapp != nil
}
func (m *Manifest) MeetMinServerVersion(serverVersion string) (bool, error) {
minServerVersion, err := semver.Parse(m.MinServerVersion)
if err != nil {
return false, errors.New("failed to parse MinServerVersion")
}
sv := semver.MustParse(serverVersion)
if sv.LT(minServerVersion) {
return false, nil
}
return true, nil
}
func (m *Manifest) IsValid() error {
if !IsValidPluginId(m.Id) {
return errors.New("invalid plugin ID")
}
if m.HomepageURL != "" && !IsValidHttpUrl(m.HomepageURL) {
return errors.New("invalid HomepageURL")
}
if m.SupportURL != "" && !IsValidHttpUrl(m.SupportURL) {
return errors.New("invalid SupportURL")
}
if m.ReleaseNotesURL != "" && !IsValidHttpUrl(m.ReleaseNotesURL) {
return errors.New("invalid ReleaseNotesURL")
}
if m.Version != "" {
_, err := semver.Parse(m.Version)
if err != nil {
return errors.Wrap(err, "failed to parse Version")
}
}
if m.MinServerVersion != "" {
_, err := semver.Parse(m.MinServerVersion)
if err != nil {
return errors.Wrap(err, "failed to parse MinServerVersion")
}
}
if m.SettingsSchema != nil {
err := m.SettingsSchema.isValid()
if err != nil {
return errors.Wrap(err, "invalid settings schema")
}
}
return nil
}
func (s *PluginSettingsSchema) isValid() error {
for _, setting := range s.Settings {
err := setting.isValid()
if err != nil {
return err
}
}
return nil
}
func (s *PluginSetting) isValid() error {
pluginSettingType, err := convertTypeToPluginSettingType(s.Type)
if err != nil {
return err
}
if s.RegenerateHelpText != "" && pluginSettingType != Generated {
return errors.New("should not set RegenerateHelpText for setting type that is not generated")
}
if s.Placeholder != "" && !(pluginSettingType == Generated ||
pluginSettingType == Text ||
pluginSettingType == LongText ||
pluginSettingType == Number ||
pluginSettingType == Username) {
return errors.New("should not set Placeholder for setting type not in text, generated or username")
}
if s.Options != nil {
if pluginSettingType != Radio && pluginSettingType != Dropdown {
return errors.New("should not set Options for setting type not in radio or dropdown")
}
for _, option := range s.Options {
if option.DisplayName == "" || option.Value == "" {
return errors.New("should not have empty Displayname or Value for any option")
}
}
}
return nil
}
func convertTypeToPluginSettingType(t string) (PluginSettingType, error) {
var settingType PluginSettingType
switch t {
case "bool":
return Bool, nil
case "dropdown":
return Dropdown, nil
case "generated":
return Generated, nil
case "radio":
return Radio, nil
case "text":
return Text, nil
case "number":
return Number, nil
case "longtext":
return LongText, nil
case "username":
return Username, nil
case "custom":
return Custom, nil
default:
return settingType, errors.New("invalid setting type: " + t)
}
}
// FindManifest will find and parse the manifest in a given directory.
//
// In all cases other than a does-not-exist error, path is set to the path of the manifest file that was
// found.
//
// Manifests are JSON or YAML files named plugin.json, plugin.yaml, or plugin.yml.
func FindManifest(dir string) (manifest *Manifest, path string, err error) {
for _, name := range []string{"plugin.yml", "plugin.yaml"} {
path = filepath.Join(dir, name)
f, ferr := os.Open(path)
if ferr != nil {
if !os.IsNotExist(ferr) {
return nil, "", ferr
}
continue
}
b, ioerr := ioutil.ReadAll(f)
f.Close()
if ioerr != nil {
return nil, path, ioerr
}
var parsed Manifest
err = yaml.Unmarshal(b, &parsed)
if err != nil {
return nil, path, err
}
manifest = &parsed
manifest.Id = strings.ToLower(manifest.Id)
return manifest, path, nil
}
path = filepath.Join(dir, "plugin.json")
f, ferr := os.Open(path)
if ferr != nil {
if os.IsNotExist(ferr) {
path = ""
}
return nil, path, ferr
}
defer f.Close()
var parsed Manifest
err = json.NewDecoder(f).Decode(&parsed)
if err != nil {
return nil, path, err
}
manifest = &parsed
manifest.Id = strings.ToLower(manifest.Id)
return manifest, path, nil
}

View File

@ -0,0 +1,124 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"bytes"
"encoding/base64"
"encoding/json"
"io"
"net/url"
"strconv"
"github.com/pkg/errors"
)
// BaseMarketplacePlugin is a Mattermost plugin received from the Marketplace server.
type BaseMarketplacePlugin struct {
HomepageURL string `json:"homepage_url"`
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.
Manifest *Manifest `json:"manifest"`
}
// MarketplaceLabel represents a label shown in the Marketplace UI.
type MarketplaceLabel struct {
Name string `json:"name"`
Description string `json:"description"`
URL string `json:"url"`
Color string `json:"color"`
}
// MarketplacePlugin is a state aware Marketplace plugin.
type MarketplacePlugin struct {
*BaseMarketplacePlugin
InstalledVersion string `json:"installed_version"`
}
// BaseMarketplacePluginsFromReader decodes a json-encoded list of plugins from the given io.Reader.
func BaseMarketplacePluginsFromReader(reader io.Reader) ([]*BaseMarketplacePlugin, error) {
plugins := []*BaseMarketplacePlugin{}
decoder := json.NewDecoder(reader)
if err := decoder.Decode(&plugins); err != nil && err != io.EOF {
return nil, err
}
return plugins, nil
}
// MarketplacePluginsFromReader decodes a json-encoded list of plugins from the given io.Reader.
func MarketplacePluginsFromReader(reader io.Reader) ([]*MarketplacePlugin, error) {
plugins := []*MarketplacePlugin{}
decoder := json.NewDecoder(reader)
if err := decoder.Decode(&plugins); err != nil && err != io.EOF {
return nil, err
}
return plugins, nil
}
// DecodeSignature Decodes signature and returns ReadSeeker.
func (plugin *BaseMarketplacePlugin) DecodeSignature() (io.ReadSeeker, error) {
signatureBytes, err := base64.StdEncoding.DecodeString(plugin.Signature)
if err != nil {
return nil, errors.Wrap(err, "Unable to decode base64 signature.")
}
return bytes.NewReader(signatureBytes), nil
}
// MarketplacePluginFilter describes the parameters to request a list of plugins.
type MarketplacePluginFilter struct {
Page int
PerPage int
Filter string
ServerVersion string
BuildEnterpriseReady bool
EnterprisePlugins bool
LocalOnly bool
}
// ApplyToURL modifies the given url to include query string parameters for the request.
func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
q := u.Query()
q.Add("page", strconv.Itoa(filter.Page))
if filter.PerPage > 0 {
q.Add("per_page", strconv.Itoa(filter.PerPage))
}
q.Add("filter", filter.Filter)
q.Add("server_version", filter.ServerVersion)
q.Add("build_enterprise_ready", strconv.FormatBool(filter.BuildEnterpriseReady))
q.Add("enterprise_plugins", strconv.FormatBool(filter.EnterprisePlugins))
q.Add("local_only", strconv.FormatBool(filter.LocalOnly))
u.RawQuery = q.Encode()
}
// InstallMarketplacePluginRequest struct describes parameters of the requested plugin.
type InstallMarketplacePluginRequest struct {
Id string `json:"id"`
Version string `json:"version"`
}
// PluginRequestFromReader decodes a json-encoded plugin request from the given io.Reader.
func PluginRequestFromReader(reader io.Reader) (*InstallMarketplacePluginRequest, error) {
var r *InstallMarketplacePluginRequest
err := json.NewDecoder(reader).Decode(&r)
if err != nil {
return nil, err
}
return r, nil
}
// ToJson method will return json from plugin request.
func (r *InstallMarketplacePluginRequest) ToJson() (string, error) {
b, err := json.Marshal(r)
if err != nil {
return "", err
}
return string(b), nil
}

View File

@ -0,0 +1,80 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"fmt"
"net/url"
)
type UserMentionMap map[string]string
type ChannelMentionMap map[string]string
const (
userMentionsKey = "user_mentions"
userMentionsIdsKey = "user_mentions_ids"
channelMentionsKey = "channel_mentions"
channelMentionsIdsKey = "channel_mentions_ids"
)
func UserMentionMapFromURLValues(values url.Values) (UserMentionMap, error) {
return mentionsFromURLValues(values, userMentionsKey, userMentionsIdsKey)
}
func (m UserMentionMap) ToURLValues() url.Values {
return mentionsToURLValues(m, userMentionsKey, userMentionsIdsKey)
}
func ChannelMentionMapFromURLValues(values url.Values) (ChannelMentionMap, error) {
return mentionsFromURLValues(values, channelMentionsKey, channelMentionsIdsKey)
}
func (m ChannelMentionMap) ToURLValues() url.Values {
return mentionsToURLValues(m, channelMentionsKey, channelMentionsIdsKey)
}
func mentionsFromURLValues(values url.Values, mentionKey, idKey string) (map[string]string, error) {
mentions, mentionsOk := values[mentionKey]
ids, idsOk := values[idKey]
if !mentionsOk && !idsOk {
return map[string]string{}, nil
}
if !mentionsOk {
return nil, fmt.Errorf("%s key not found", mentionKey)
}
if !idsOk {
return nil, fmt.Errorf("%s key not found", idKey)
}
if len(mentions) != len(ids) {
return nil, fmt.Errorf("keys %s and %s have different length", mentionKey, idKey)
}
mentionsMap := make(map[string]string)
for i, mention := range mentions {
id := ids[i]
if oldId, ok := mentionsMap[mention]; ok && oldId != id {
return nil, fmt.Errorf("key %s has two different values: %s and %s", mention, oldId, id)
}
mentionsMap[mention] = id
}
return mentionsMap, nil
}
func mentionsToURLValues(mentions map[string]string, mentionKey, idKey string) url.Values {
values := url.Values{}
for mention, id := range mentions {
values.Add(mentionKey, mention)
values.Add(idKey, id)
}
return values
}

View File

@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
type MessageExport struct {
TeamId *string
TeamName *string
TeamDisplayName *string
ChannelId *string
ChannelName *string
ChannelDisplayName *string
ChannelType *string
UserId *string
UserEmail *string
Username *string
IsBot bool
PostId *string
PostCreateAt *int64
PostUpdateAt *int64
PostDeleteAt *int64
PostMessage *string
PostType *string
PostRootId *string
PostProps *string
PostOriginalId *string
PostFileIds StringArray
}

View File

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

View File

@ -0,0 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
const (
MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2 = "migration_advanced_permissions_phase_2"
MIGRATION_KEY_EMOJI_PERMISSIONS_SPLIT = "emoji_permissions_split"
MIGRATION_KEY_WEBHOOK_PERMISSIONS_SPLIT = "webhook_permissions_split"
MIGRATION_KEY_LIST_JOIN_PUBLIC_PRIVATE_TEAMS = "list_join_public_private_teams"
MIGRATION_KEY_REMOVE_PERMANENT_DELETE_USER = "remove_permanent_delete_user"
MIGRATION_KEY_ADD_BOT_PERMISSIONS = "add_bot_permissions"
MIGRATION_KEY_APPLY_CHANNEL_MANAGE_DELETE_TO_CHANNEL_USER = "apply_channel_manage_delete_to_channel_user"
MIGRATION_KEY_REMOVE_CHANNEL_MANAGE_DELETE_FROM_TEAM_USER = "remove_channel_manage_delete_from_team_user"
MIGRATION_KEY_VIEW_MEMBERS_NEW_PERMISSION = "view_members_new_permission"
MIGRATION_KEY_ADD_MANAGE_GUESTS_PERMISSIONS = "add_manage_guests_permissions"
MIGRATION_KEY_CHANNEL_MODERATIONS_PERMISSIONS = "channel_moderations_permissions"
MIGRATION_KEY_ADD_USE_GROUP_MENTIONS_PERMISSION = "add_use_group_mentions_permission"
)

View File

@ -0,0 +1,151 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"unicode/utf8"
)
const (
OAUTH_ACTION_SIGNUP = "signup"
OAUTH_ACTION_LOGIN = "login"
OAUTH_ACTION_EMAIL_TO_SSO = "email_to_sso"
OAUTH_ACTION_SSO_TO_EMAIL = "sso_to_email"
OAUTH_ACTION_MOBILE = "mobile"
)
type OAuthApp struct {
Id string `json:"id"`
CreatorId string `json:"creator_id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
ClientSecret string `json:"client_secret"`
Name string `json:"name"`
Description string `json:"description"`
IconURL string `json:"icon_url"`
CallbackUrls StringArray `json:"callback_urls"`
Homepage string `json:"homepage"`
IsTrusted bool `json:"is_trusted"`
}
// IsValid validates the app and returns an error if it isn't configured
// correctly.
func (a *OAuthApp) IsValid() *AppError {
if !IsValidId(a.Id) {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "", http.StatusBadRequest)
}
if a.CreateAt == 0 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if a.UpdateAt == 0 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if !IsValidId(a.CreatorId) {
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 {
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 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
for _, callback := range a.CallbackUrls {
if !IsValidHttpUrl(callback) {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
}
}
if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(a.Description) > 512 {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.IconURL) > 0 {
if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) {
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
}
return nil
}
// PreSave will set the Id and ClientSecret if missing. It will also fill
// in the CreateAt, UpdateAt times. It should be run before saving the app to the db.
func (a *OAuthApp) PreSave() {
if a.Id == "" {
a.Id = NewId()
}
if a.ClientSecret == "" {
a.ClientSecret = NewId()
}
a.CreateAt = GetMillis()
a.UpdateAt = a.CreateAt
}
// PreUpdate should be run before updating the app in the db.
func (a *OAuthApp) PreUpdate() {
a.UpdateAt = GetMillis()
}
func (a *OAuthApp) ToJson() string {
b, _ := json.Marshal(a)
return string(b)
}
// Generate a valid strong etag so the browser can cache the results
func (a *OAuthApp) Etag() string {
return Etag(a.Id, a.UpdateAt)
}
// Remove any private data from the app object
func (a *OAuthApp) Sanitize() {
a.ClientSecret = ""
}
func (a *OAuthApp) IsValidRedirectURL(url string) bool {
for _, u := range a.CallbackUrls {
if u == url {
return true
}
}
return false
}
func OAuthAppFromJson(data io.Reader) *OAuthApp {
var app *OAuthApp
json.NewDecoder(data).Decode(&app)
return app
}
func OAuthAppListToJson(l []*OAuthApp) string {
b, _ := json.Marshal(l)
return string(b)
}
func OAuthAppListFromJson(data io.Reader) []*OAuthApp {
var o []*OAuthApp
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,264 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
)
type OutgoingWebhook struct {
Id string `json:"id"`
Token string `json:"token"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
CreatorId string `json:"creator_id"`
ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"`
TriggerWords StringArray `json:"trigger_words"`
TriggerWhen int `json:"trigger_when"`
CallbackURLs StringArray `json:"callback_urls"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
ContentType string `json:"content_type"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
}
type OutgoingWebhookPayload struct {
Token string `json:"token"`
TeamId string `json:"team_id"`
TeamDomain string `json:"team_domain"`
ChannelId string `json:"channel_id"`
ChannelName string `json:"channel_name"`
Timestamp int64 `json:"timestamp"`
UserId string `json:"user_id"`
UserName string `json:"user_name"`
PostId string `json:"post_id"`
Text string `json:"text"`
TriggerWord string `json:"trigger_word"`
FileIds string `json:"file_ids"`
}
type OutgoingWebhookResponse struct {
Text *string `json:"text"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
Props StringInterface `json:"props"`
Attachments []*SlackAttachment `json:"attachments"`
Type string `json:"type"`
ResponseType string `json:"response_type"`
}
const OUTGOING_HOOK_RESPONSE_TYPE_COMMENT = "comment"
func (o *OutgoingWebhookPayload) ToJSON() string {
b, _ := json.Marshal(o)
return string(b)
}
func (o *OutgoingWebhookPayload) ToFormValues() string {
v := url.Values{}
v.Set("token", o.Token)
v.Set("team_id", o.TeamId)
v.Set("team_domain", o.TeamDomain)
v.Set("channel_id", o.ChannelId)
v.Set("channel_name", o.ChannelName)
v.Set("timestamp", strconv.FormatInt(o.Timestamp/1000, 10))
v.Set("user_id", o.UserId)
v.Set("user_name", o.UserName)
v.Set("post_id", o.PostId)
v.Set("text", o.Text)
v.Set("trigger_word", o.TriggerWord)
v.Set("file_ids", o.FileIds)
return v.Encode()
}
func (o *OutgoingWebhook) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func OutgoingWebhookFromJson(data io.Reader) *OutgoingWebhook {
var o *OutgoingWebhook
json.NewDecoder(data).Decode(&o)
return o
}
func OutgoingWebhookListToJson(l []*OutgoingWebhook) string {
b, _ := json.Marshal(l)
return string(b)
}
func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook {
var o []*OutgoingWebhook
json.NewDecoder(data).Decode(&o)
return o
}
func (o *OutgoingWebhookResponse) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func OutgoingWebhookResponseFromJson(data io.Reader) (*OutgoingWebhookResponse, error) {
var o *OutgoingWebhookResponse
err := json.NewDecoder(data).Decode(&o)
return o, err
}
func (o *OutgoingWebhook) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Token) != 26 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidId(o.CreatorId) {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ChannelId) != 0 && !IsValidId(o.ChannelId) {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.TeamId) {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
}
if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest)
}
if len(o.TriggerWords) != 0 {
for _, triggerWord := range o.TriggerWords {
if len(triggerWord) == 0 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest)
}
}
}
if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
}
for _, callback := range o.CallbackURLs {
if !IsValidHttpUrl(callback) {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest)
}
}
if len(o.DisplayName) > 64 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Description) > 500 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ContentType) > 128 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
}
if o.TriggerWhen > 1 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Username) > 64 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.username.app_error", nil, "", http.StatusBadRequest)
}
if len(o.IconURL) > 1024 {
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.icon_url.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *OutgoingWebhook) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.Token == "" {
o.Token = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
}
func (o *OutgoingWebhook) PreUpdate() {
o.UpdateAt = GetMillis()
}
func (o *OutgoingWebhook) TriggerWordExactMatch(word string) bool {
if len(word) == 0 {
return false
}
for _, trigger := range o.TriggerWords {
if trigger == word {
return true
}
}
return false
}
func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
if len(word) == 0 {
return false
}
for _, trigger := range o.TriggerWords {
if strings.HasPrefix(word, trigger) {
return true
}
}
return false
}
func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) {
if len(word) == 0 {
return
}
if isExactMatch {
for _, trigger := range o.TriggerWords {
if trigger == word {
triggerWord = trigger
break
}
}
} else {
for _, trigger := range o.TriggerWords {
if strings.HasPrefix(word, trigger) {
triggerWord = trigger
break
}
}
}
return triggerWord
}

View File

@ -0,0 +1,676 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
const (
PERMISSION_SCOPE_SYSTEM = "system_scope"
PERMISSION_SCOPE_TEAM = "team_scope"
PERMISSION_SCOPE_CHANNEL = "channel_scope"
)
type Permission struct {
Id string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Scope string `json:"scope"`
}
var PERMISSION_INVITE_USER *Permission
var PERMISSION_ADD_USER_TO_TEAM *Permission
var PERMISSION_USE_SLASH_COMMANDS *Permission
var PERMISSION_MANAGE_SLASH_COMMANDS *Permission
var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission
var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission
var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission
var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission
var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission
var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission
var PERMISSION_MANAGE_ROLES *Permission
var PERMISSION_MANAGE_TEAM_ROLES *Permission
var PERMISSION_MANAGE_CHANNEL_ROLES *Permission
var PERMISSION_CREATE_DIRECT_CHANNEL *Permission
var PERMISSION_CREATE_GROUP_CHANNEL *Permission
var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission
var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission
var PERMISSION_LIST_PUBLIC_TEAMS *Permission
var PERMISSION_JOIN_PUBLIC_TEAMS *Permission
var PERMISSION_LIST_PRIVATE_TEAMS *Permission
var PERMISSION_JOIN_PRIVATE_TEAMS *Permission
var PERMISSION_LIST_TEAM_CHANNELS *Permission
var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission
var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission
var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission
var PERMISSION_EDIT_OTHER_USERS *Permission
var PERMISSION_READ_CHANNEL *Permission
var PERMISSION_READ_PUBLIC_CHANNEL *Permission
var PERMISSION_ADD_REACTION *Permission
var PERMISSION_REMOVE_REACTION *Permission
var PERMISSION_REMOVE_OTHERS_REACTIONS *Permission
var PERMISSION_PERMANENT_DELETE_USER *Permission
var PERMISSION_UPLOAD_FILE *Permission
var PERMISSION_GET_PUBLIC_LINK *Permission
var PERMISSION_MANAGE_WEBHOOKS *Permission
var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission
var PERMISSION_MANAGE_INCOMING_WEBHOOKS *Permission
var PERMISSION_MANAGE_OUTGOING_WEBHOOKS *Permission
var PERMISSION_MANAGE_OTHERS_INCOMING_WEBHOOKS *Permission
var PERMISSION_MANAGE_OTHERS_OUTGOING_WEBHOOKS *Permission
var PERMISSION_MANAGE_OAUTH *Permission
var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission
var PERMISSION_MANAGE_EMOJIS *Permission
var PERMISSION_MANAGE_OTHERS_EMOJIS *Permission
var PERMISSION_CREATE_EMOJIS *Permission
var PERMISSION_DELETE_EMOJIS *Permission
var PERMISSION_DELETE_OTHERS_EMOJIS *Permission
var PERMISSION_CREATE_POST *Permission
var PERMISSION_CREATE_POST_PUBLIC *Permission
var PERMISSION_CREATE_POST_EPHEMERAL *Permission
var PERMISSION_EDIT_POST *Permission
var PERMISSION_EDIT_OTHERS_POSTS *Permission
var PERMISSION_DELETE_POST *Permission
var PERMISSION_DELETE_OTHERS_POSTS *Permission
var PERMISSION_REMOVE_USER_FROM_TEAM *Permission
var PERMISSION_CREATE_TEAM *Permission
var PERMISSION_MANAGE_TEAM *Permission
var PERMISSION_IMPORT_TEAM *Permission
var PERMISSION_VIEW_TEAM *Permission
var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission
var PERMISSION_MANAGE_JOBS *Permission
var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission
var PERMISSION_READ_USER_ACCESS_TOKEN *Permission
var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission
var PERMISSION_CREATE_BOT *Permission
var PERMISSION_ASSIGN_BOT *Permission
var PERMISSION_READ_BOTS *Permission
var PERMISSION_READ_OTHERS_BOTS *Permission
var PERMISSION_MANAGE_BOTS *Permission
var PERMISSION_MANAGE_OTHERS_BOTS *Permission
var PERMISSION_VIEW_MEMBERS *Permission
var PERMISSION_INVITE_GUEST *Permission
var PERMISSION_PROMOTE_GUEST *Permission
var PERMISSION_DEMOTE_TO_GUEST *Permission
var PERMISSION_USE_CHANNEL_MENTIONS *Permission
var PERMISSION_USE_GROUP_MENTIONS *Permission
// General permission that encompasses all system admin functions
// in the future this could be broken up to allow access to some
// admin functions but not others
var PERMISSION_MANAGE_SYSTEM *Permission
var ALL_PERMISSIONS []*Permission
var CHANNEL_MODERATED_PERMISSIONS []string
var CHANNEL_MODERATED_PERMISSIONS_MAP map[string]string
func initializePermissions() {
PERMISSION_INVITE_USER = &Permission{
"invite_user",
"authentication.permissions.team_invite_user.name",
"authentication.permissions.team_invite_user.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_ADD_USER_TO_TEAM = &Permission{
"add_user_to_team",
"authentication.permissions.add_user_to_team.name",
"authentication.permissions.add_user_to_team.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_USE_SLASH_COMMANDS = &Permission{
"use_slash_commands",
"authentication.permissions.team_use_slash_commands.name",
"authentication.permissions.team_use_slash_commands.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{
"manage_slash_commands",
"authentication.permissions.manage_slash_commands.name",
"authentication.permissions.manage_slash_commands.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{
"manage_others_slash_commands",
"authentication.permissions.manage_others_slash_commands.name",
"authentication.permissions.manage_others_slash_commands.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{
"create_public_channel",
"authentication.permissions.create_public_channel.name",
"authentication.permissions.create_public_channel.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{
"create_private_channel",
"authentication.permissions.create_private_channel.name",
"authentication.permissions.create_private_channel.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{
"manage_public_channel_members",
"authentication.permissions.manage_public_channel_members.name",
"authentication.permissions.manage_public_channel_members.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{
"manage_private_channel_members",
"authentication.permissions.manage_private_channel_members.name",
"authentication.permissions.manage_private_channel_members.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{
"assign_system_admin_role",
"authentication.permissions.assign_system_admin_role.name",
"authentication.permissions.assign_system_admin_role.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_ROLES = &Permission{
"manage_roles",
"authentication.permissions.manage_roles.name",
"authentication.permissions.manage_roles.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_TEAM_ROLES = &Permission{
"manage_team_roles",
"authentication.permissions.manage_team_roles.name",
"authentication.permissions.manage_team_roles.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_CHANNEL_ROLES = &Permission{
"manage_channel_roles",
"authentication.permissions.manage_channel_roles.name",
"authentication.permissions.manage_channel_roles.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_MANAGE_SYSTEM = &Permission{
"manage_system",
"authentication.permissions.manage_system.name",
"authentication.permissions.manage_system.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{
"create_direct_channel",
"authentication.permissions.create_direct_channel.name",
"authentication.permissions.create_direct_channel.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_CREATE_GROUP_CHANNEL = &Permission{
"create_group_channel",
"authentication.permissions.create_group_channel.name",
"authentication.permissions.create_group_channel.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{
"manage_public_channel_properties",
"authentication.permissions.manage_public_channel_properties.name",
"authentication.permissions.manage_public_channel_properties.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{
"manage_private_channel_properties",
"authentication.permissions.manage_private_channel_properties.name",
"authentication.permissions.manage_private_channel_properties.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_LIST_PUBLIC_TEAMS = &Permission{
"list_public_teams",
"authentication.permissions.list_public_teams.name",
"authentication.permissions.list_public_teams.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_JOIN_PUBLIC_TEAMS = &Permission{
"join_public_teams",
"authentication.permissions.join_public_teams.name",
"authentication.permissions.join_public_teams.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_LIST_PRIVATE_TEAMS = &Permission{
"list_private_teams",
"authentication.permissions.list_private_teams.name",
"authentication.permissions.list_private_teams.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_JOIN_PRIVATE_TEAMS = &Permission{
"join_private_teams",
"authentication.permissions.join_private_teams.name",
"authentication.permissions.join_private_teams.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_LIST_TEAM_CHANNELS = &Permission{
"list_team_channels",
"authentication.permissions.list_team_channels.name",
"authentication.permissions.list_team_channels.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{
"join_public_channels",
"authentication.permissions.join_public_channels.name",
"authentication.permissions.join_public_channels.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{
"delete_public_channel",
"authentication.permissions.delete_public_channel.name",
"authentication.permissions.delete_public_channel.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{
"delete_private_channel",
"authentication.permissions.delete_private_channel.name",
"authentication.permissions.delete_private_channel.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_EDIT_OTHER_USERS = &Permission{
"edit_other_users",
"authentication.permissions.edit_other_users.name",
"authentication.permissions.edit_other_users.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_READ_CHANNEL = &Permission{
"read_channel",
"authentication.permissions.read_channel.name",
"authentication.permissions.read_channel.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_READ_PUBLIC_CHANNEL = &Permission{
"read_public_channel",
"authentication.permissions.read_public_channel.name",
"authentication.permissions.read_public_channel.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_ADD_REACTION = &Permission{
"add_reaction",
"authentication.permissions.add_reaction.name",
"authentication.permissions.add_reaction.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_REMOVE_REACTION = &Permission{
"remove_reaction",
"authentication.permissions.remove_reaction.name",
"authentication.permissions.remove_reaction.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_REMOVE_OTHERS_REACTIONS = &Permission{
"remove_others_reactions",
"authentication.permissions.remove_others_reactions.name",
"authentication.permissions.remove_others_reactions.description",
PERMISSION_SCOPE_CHANNEL,
}
// DEPRECATED
PERMISSION_PERMANENT_DELETE_USER = &Permission{
"permanent_delete_user",
"authentication.permissions.permanent_delete_user.name",
"authentication.permissions.permanent_delete_user.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_UPLOAD_FILE = &Permission{
"upload_file",
"authentication.permissions.upload_file.name",
"authentication.permissions.upload_file.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_GET_PUBLIC_LINK = &Permission{
"get_public_link",
"authentication.permissions.get_public_link.name",
"authentication.permissions.get_public_link.description",
PERMISSION_SCOPE_SYSTEM,
}
// DEPRECATED
PERMISSION_MANAGE_WEBHOOKS = &Permission{
"manage_webhooks",
"authentication.permissions.manage_webhooks.name",
"authentication.permissions.manage_webhooks.description",
PERMISSION_SCOPE_TEAM,
}
// DEPRECATED
PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{
"manage_others_webhooks",
"authentication.permissions.manage_others_webhooks.name",
"authentication.permissions.manage_others_webhooks.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_INCOMING_WEBHOOKS = &Permission{
"manage_incoming_webhooks",
"authentication.permissions.manage_incoming_webhooks.name",
"authentication.permissions.manage_incoming_webhooks.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_OUTGOING_WEBHOOKS = &Permission{
"manage_outgoing_webhooks",
"authentication.permissions.manage_outgoing_webhooks.name",
"authentication.permissions.manage_outgoing_webhooks.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_OTHERS_INCOMING_WEBHOOKS = &Permission{
"manage_others_incoming_webhooks",
"authentication.permissions.manage_others_incoming_webhooks.name",
"authentication.permissions.manage_others_incoming_webhooks.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_OTHERS_OUTGOING_WEBHOOKS = &Permission{
"manage_others_outgoing_webhooks",
"authentication.permissions.manage_others_outgoing_webhooks.name",
"authentication.permissions.manage_others_outgoing_webhooks.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_MANAGE_OAUTH = &Permission{
"manage_oauth",
"authentication.permissions.manage_oauth.name",
"authentication.permissions.manage_oauth.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{
"manage_system_wide_oauth",
"authentication.permissions.manage_system_wide_oauth.name",
"authentication.permissions.manage_system_wide_oauth.description",
PERMISSION_SCOPE_SYSTEM,
}
// DEPRECATED
PERMISSION_MANAGE_EMOJIS = &Permission{
"manage_emojis",
"authentication.permissions.manage_emojis.name",
"authentication.permissions.manage_emojis.description",
PERMISSION_SCOPE_TEAM,
}
// DEPRECATED
PERMISSION_MANAGE_OTHERS_EMOJIS = &Permission{
"manage_others_emojis",
"authentication.permissions.manage_others_emojis.name",
"authentication.permissions.manage_others_emojis.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_CREATE_EMOJIS = &Permission{
"create_emojis",
"authentication.permissions.create_emojis.name",
"authentication.permissions.create_emojis.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_DELETE_EMOJIS = &Permission{
"delete_emojis",
"authentication.permissions.delete_emojis.name",
"authentication.permissions.delete_emojis.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_DELETE_OTHERS_EMOJIS = &Permission{
"delete_others_emojis",
"authentication.permissions.delete_others_emojis.name",
"authentication.permissions.delete_others_emojis.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_CREATE_POST = &Permission{
"create_post",
"authentication.permissions.create_post.name",
"authentication.permissions.create_post.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_CREATE_POST_PUBLIC = &Permission{
"create_post_public",
"authentication.permissions.create_post_public.name",
"authentication.permissions.create_post_public.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_CREATE_POST_EPHEMERAL = &Permission{
"create_post_ephemeral",
"authentication.permissions.create_post_ephemeral.name",
"authentication.permissions.create_post_ephemeral.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_EDIT_POST = &Permission{
"edit_post",
"authentication.permissions.edit_post.name",
"authentication.permissions.edit_post.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_EDIT_OTHERS_POSTS = &Permission{
"edit_others_posts",
"authentication.permissions.edit_others_posts.name",
"authentication.permissions.edit_others_posts.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_DELETE_POST = &Permission{
"delete_post",
"authentication.permissions.delete_post.name",
"authentication.permissions.delete_post.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_DELETE_OTHERS_POSTS = &Permission{
"delete_others_posts",
"authentication.permissions.delete_others_posts.name",
"authentication.permissions.delete_others_posts.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{
"remove_user_from_team",
"authentication.permissions.remove_user_from_team.name",
"authentication.permissions.remove_user_from_team.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_CREATE_TEAM = &Permission{
"create_team",
"authentication.permissions.create_team.name",
"authentication.permissions.create_team.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_TEAM = &Permission{
"manage_team",
"authentication.permissions.manage_team.name",
"authentication.permissions.manage_team.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_IMPORT_TEAM = &Permission{
"import_team",
"authentication.permissions.import_team.name",
"authentication.permissions.import_team.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_VIEW_TEAM = &Permission{
"view_team",
"authentication.permissions.view_team.name",
"authentication.permissions.view_team.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{
"list_users_without_team",
"authentication.permissions.list_users_without_team.name",
"authentication.permissions.list_users_without_team.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{
"create_user_access_token",
"authentication.permissions.create_user_access_token.name",
"authentication.permissions.create_user_access_token.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{
"read_user_access_token",
"authentication.permissions.read_user_access_token.name",
"authentication.permissions.read_user_access_token.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{
"revoke_user_access_token",
"authentication.permissions.revoke_user_access_token.name",
"authentication.permissions.revoke_user_access_token.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_CREATE_BOT = &Permission{
"create_bot",
"authentication.permissions.create_bot.name",
"authentication.permissions.create_bot.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_ASSIGN_BOT = &Permission{
"assign_bot",
"authentication.permissions.assign_bot.name",
"authentication.permissions.assign_bot.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_READ_BOTS = &Permission{
"read_bots",
"authentication.permissions.read_bots.name",
"authentication.permissions.read_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_READ_OTHERS_BOTS = &Permission{
"read_others_bots",
"authentication.permissions.read_others_bots.name",
"authentication.permissions.read_others_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_BOTS = &Permission{
"manage_bots",
"authentication.permissions.manage_bots.name",
"authentication.permissions.manage_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_OTHERS_BOTS = &Permission{
"manage_others_bots",
"authentication.permissions.manage_others_bots.name",
"authentication.permissions.manage_others_bots.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_MANAGE_JOBS = &Permission{
"manage_jobs",
"authentication.permisssions.manage_jobs.name",
"authentication.permisssions.manage_jobs.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_VIEW_MEMBERS = &Permission{
"view_members",
"authentication.permisssions.view_members.name",
"authentication.permisssions.view_members.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_INVITE_GUEST = &Permission{
"invite_guest",
"authentication.permissions.invite_guest.name",
"authentication.permissions.invite_guest.description",
PERMISSION_SCOPE_TEAM,
}
PERMISSION_PROMOTE_GUEST = &Permission{
"promote_guest",
"authentication.permissions.promote_guest.name",
"authentication.permissions.promote_guest.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_DEMOTE_TO_GUEST = &Permission{
"demote_to_guest",
"authentication.permissions.demote_to_guest.name",
"authentication.permissions.demote_to_guest.description",
PERMISSION_SCOPE_SYSTEM,
}
PERMISSION_USE_CHANNEL_MENTIONS = &Permission{
"use_channel_mentions",
"authentication.permissions.use_channel_mentions.name",
"authentication.permissions.use_channel_mentions.description",
PERMISSION_SCOPE_CHANNEL,
}
PERMISSION_USE_GROUP_MENTIONS = &Permission{
"use_group_mentions",
"authentication.permissions.use_group_mentions.name",
"authentication.permissions.use_group_mentions.description",
PERMISSION_SCOPE_CHANNEL,
}
ALL_PERMISSIONS = []*Permission{
PERMISSION_INVITE_USER,
PERMISSION_ADD_USER_TO_TEAM,
PERMISSION_USE_SLASH_COMMANDS,
PERMISSION_MANAGE_SLASH_COMMANDS,
PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS,
PERMISSION_CREATE_PUBLIC_CHANNEL,
PERMISSION_CREATE_PRIVATE_CHANNEL,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS,
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS,
PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE,
PERMISSION_MANAGE_ROLES,
PERMISSION_MANAGE_TEAM_ROLES,
PERMISSION_MANAGE_CHANNEL_ROLES,
PERMISSION_CREATE_DIRECT_CHANNEL,
PERMISSION_CREATE_GROUP_CHANNEL,
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES,
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES,
PERMISSION_LIST_PUBLIC_TEAMS,
PERMISSION_JOIN_PUBLIC_TEAMS,
PERMISSION_LIST_PRIVATE_TEAMS,
PERMISSION_JOIN_PRIVATE_TEAMS,
PERMISSION_LIST_TEAM_CHANNELS,
PERMISSION_JOIN_PUBLIC_CHANNELS,
PERMISSION_DELETE_PUBLIC_CHANNEL,
PERMISSION_DELETE_PRIVATE_CHANNEL,
PERMISSION_EDIT_OTHER_USERS,
PERMISSION_READ_CHANNEL,
PERMISSION_READ_PUBLIC_CHANNEL,
PERMISSION_ADD_REACTION,
PERMISSION_REMOVE_REACTION,
PERMISSION_REMOVE_OTHERS_REACTIONS,
PERMISSION_PERMANENT_DELETE_USER,
PERMISSION_UPLOAD_FILE,
PERMISSION_GET_PUBLIC_LINK,
PERMISSION_MANAGE_WEBHOOKS,
PERMISSION_MANAGE_OTHERS_WEBHOOKS,
PERMISSION_MANAGE_INCOMING_WEBHOOKS,
PERMISSION_MANAGE_OUTGOING_WEBHOOKS,
PERMISSION_MANAGE_OTHERS_INCOMING_WEBHOOKS,
PERMISSION_MANAGE_OTHERS_OUTGOING_WEBHOOKS,
PERMISSION_MANAGE_OAUTH,
PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH,
PERMISSION_MANAGE_EMOJIS,
PERMISSION_MANAGE_OTHERS_EMOJIS,
PERMISSION_CREATE_EMOJIS,
PERMISSION_DELETE_EMOJIS,
PERMISSION_DELETE_OTHERS_EMOJIS,
PERMISSION_CREATE_POST,
PERMISSION_CREATE_POST_PUBLIC,
PERMISSION_CREATE_POST_EPHEMERAL,
PERMISSION_EDIT_POST,
PERMISSION_EDIT_OTHERS_POSTS,
PERMISSION_DELETE_POST,
PERMISSION_DELETE_OTHERS_POSTS,
PERMISSION_REMOVE_USER_FROM_TEAM,
PERMISSION_CREATE_TEAM,
PERMISSION_MANAGE_TEAM,
PERMISSION_IMPORT_TEAM,
PERMISSION_VIEW_TEAM,
PERMISSION_LIST_USERS_WITHOUT_TEAM,
PERMISSION_MANAGE_JOBS,
PERMISSION_CREATE_USER_ACCESS_TOKEN,
PERMISSION_READ_USER_ACCESS_TOKEN,
PERMISSION_REVOKE_USER_ACCESS_TOKEN,
PERMISSION_CREATE_BOT,
PERMISSION_READ_BOTS,
PERMISSION_READ_OTHERS_BOTS,
PERMISSION_MANAGE_BOTS,
PERMISSION_MANAGE_OTHERS_BOTS,
PERMISSION_MANAGE_SYSTEM,
PERMISSION_VIEW_MEMBERS,
PERMISSION_INVITE_GUEST,
PERMISSION_PROMOTE_GUEST,
PERMISSION_DEMOTE_TO_GUEST,
PERMISSION_USE_CHANNEL_MENTIONS,
PERMISSION_USE_GROUP_MENTIONS,
}
CHANNEL_MODERATED_PERMISSIONS = []string{
PERMISSION_CREATE_POST.Id,
"create_reactions",
"manage_members",
PERMISSION_USE_CHANNEL_MENTIONS.Id,
}
CHANNEL_MODERATED_PERMISSIONS_MAP = map[string]string{
PERMISSION_CREATE_POST.Id: CHANNEL_MODERATED_PERMISSIONS[0],
PERMISSION_ADD_REACTION.Id: CHANNEL_MODERATED_PERMISSIONS[1],
PERMISSION_REMOVE_REACTION.Id: CHANNEL_MODERATED_PERMISSIONS[1],
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id: CHANNEL_MODERATED_PERMISSIONS[2],
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id: CHANNEL_MODERATED_PERMISSIONS[2],
PERMISSION_USE_CHANNEL_MENTIONS.Id: CHANNEL_MODERATED_PERMISSIONS[3],
}
}
func init() {
initializePermissions()
}

View File

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
// PluginEventData used to notify peers about plugin changes.
type PluginEventData struct {
Id string `json:"id"`
}
func (p *PluginEventData) ToJson() string {
b, _ := json.Marshal(p)
return string(b)
}
func PluginEventDataFromJson(data io.Reader) PluginEventData {
var m PluginEventData
json.NewDecoder(data).Decode(&m)
return m
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"net/http"
"unicode/utf8"
)
const (
KEY_VALUE_PLUGIN_ID_MAX_RUNES = 190
KEY_VALUE_KEY_MAX_RUNES = 50
)
type PluginKeyValue struct {
PluginId string `json:"plugin_id"`
Key string `json:"key" db:"PKey"`
Value []byte `json:"value" db:"PValue"`
ExpireAt int64 `json:"expire_at"`
}
func (kv *PluginKeyValue) IsValid() *AppError {
if len(kv.PluginId) == 0 || 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 {
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)
}
return nil
}

View File

@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"net/http"
)
// PluginKVSetOptions contains information on how to store a value in the plugin KV store.
type PluginKVSetOptions struct {
Atomic bool // Only store the value if the current value matches the oldValue
OldValue []byte // The value to compare with the current value. Only used when Atomic is true
ExpireInSeconds int64 // Set an expire counter
}
// IsValid returns nil if the chosen options are valid.
func (opt *PluginKVSetOptions) IsValid() *AppError {
if !opt.Atomic && opt.OldValue != nil {
return NewAppError(
"PluginKVSetOptions.IsValid",
"model.plugin_kvset_options.is_valid.old_value.app_error",
nil,
"",
http.StatusBadRequest,
)
}
return nil
}
// NewPluginKeyValueFromOptions return a PluginKeyValue given a pluginID, a KV pair and options.
func NewPluginKeyValueFromOptions(pluginId, key string, value []byte, opt PluginKVSetOptions) (*PluginKeyValue, *AppError) {
expireAt := int64(0)
if opt.ExpireInSeconds != 0 {
expireAt = GetMillis() + (opt.ExpireInSeconds * 1000)
}
kv := &PluginKeyValue{
PluginId: pluginId,
Key: key,
Value: value,
ExpireAt: expireAt,
}
return kv, nil
}

View File

@ -0,0 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
PluginStateNotRunning = 0
PluginStateStarting = 1 // unused by server
PluginStateRunning = 2
PluginStateFailedToStart = 3
PluginStateFailedToStayRunning = 4
PluginStateStopping = 5 // unused by server
)
// PluginStatus provides a cluster-aware view of installed plugins.
type PluginStatus struct {
PluginId string `json:"plugin_id"`
ClusterId string `json:"cluster_id"`
PluginPath string `json:"plugin_path"`
State int `json:"state"`
Name string `json:"name"`
Description string `json:"description"`
Version string `json:"version"`
}
type PluginStatuses []*PluginStatus
func (m *PluginStatuses) ToJson() string {
b, _ := json.Marshal(m)
return string(b)
}
func PluginStatusesFromJson(data io.Reader) PluginStatuses {
var m PluginStatuses
json.NewDecoder(data).Decode(&m)
return m
}

View File

@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"regexp"
"unicode/utf8"
)
const (
MinIdLength = 3
MaxIdLength = 190
ValidIdRegex = `^[a-zA-Z0-9-_\.]+$`
)
// ValidId constrains the set of valid plugin identifiers:
// ^[a-zA-Z0-9-_\.]+
var validId *regexp.Regexp
func init() {
validId = regexp.MustCompile(ValidIdRegex)
}
// IsValidPluginId verifies that the plugin id has a minimum length of 3, maximum length of 190, and
// contains only alphanumeric characters, dashes, underscores and periods.
//
// These constraints are necessary since the plugin id is used as part of a filesystem path.
func IsValidPluginId(id string) bool {
if utf8.RuneCountInString(id) < MinIdLength {
return false
}
if utf8.RuneCountInString(id) > MaxIdLength {
return false
}
return validId.MatchString(id)
}

View File

@ -0,0 +1,29 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type PluginInfo struct {
Manifest
}
type PluginsResponse struct {
Active []*PluginInfo `json:"active"`
Inactive []*PluginInfo `json:"inactive"`
}
func (m *PluginsResponse) ToJson() string {
b, _ := json.Marshal(m)
return string(b)
}
func PluginsResponseFromJson(data io.Reader) *PluginsResponse {
var m *PluginsResponse
json.NewDecoder(data).Decode(&m)
return m
}

View File

@ -0,0 +1,663 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"errors"
"io"
"net/http"
"regexp"
"sort"
"strings"
"sync"
"unicode/utf8"
"github.com/mattermost/mattermost-server/v5/utils/markdown"
)
const (
POST_SYSTEM_MESSAGE_PREFIX = "system_"
POST_DEFAULT = ""
POST_SLACK_ATTACHMENT = "slack_attachment"
POST_SYSTEM_GENERIC = "system_generic"
POST_JOIN_LEAVE = "system_join_leave" // Deprecated, use POST_JOIN_CHANNEL or POST_LEAVE_CHANNEL instead
POST_JOIN_CHANNEL = "system_join_channel"
POST_GUEST_JOIN_CHANNEL = "system_guest_join_channel"
POST_LEAVE_CHANNEL = "system_leave_channel"
POST_JOIN_TEAM = "system_join_team"
POST_LEAVE_TEAM = "system_leave_team"
POST_AUTO_RESPONDER = "system_auto_responder"
POST_ADD_REMOVE = "system_add_remove" // Deprecated, use POST_ADD_TO_CHANNEL or POST_REMOVE_FROM_CHANNEL instead
POST_ADD_TO_CHANNEL = "system_add_to_channel"
POST_ADD_GUEST_TO_CHANNEL = "system_add_guest_to_chan"
POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel"
POST_MOVE_CHANNEL = "system_move_channel"
POST_ADD_TO_TEAM = "system_add_to_team"
POST_REMOVE_FROM_TEAM = "system_remove_from_team"
POST_HEADER_CHANGE = "system_header_change"
POST_DISPLAYNAME_CHANGE = "system_displayname_change"
POST_CONVERT_CHANNEL = "system_convert_channel"
POST_PURPOSE_CHANGE = "system_purpose_change"
POST_CHANNEL_DELETED = "system_channel_deleted"
POST_CHANNEL_RESTORED = "system_channel_restored"
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_FILENAMES_MAX_RUNES = 4000
POST_HASHTAGS_MAX_RUNES = 1000
POST_MESSAGE_MAX_RUNES_V1 = 4000
POST_MESSAGE_MAX_BYTES_V2 = 65535 // Maximum size of a TEXT column in MySQL
POST_MESSAGE_MAX_RUNES_V2 = POST_MESSAGE_MAX_BYTES_V2 / 4 // Assume a worst-case representation
POST_PROPS_MAX_RUNES = 8000
POST_PROPS_MAX_USER_RUNES = POST_PROPS_MAX_RUNES - 400 // Leave some room for system / pre-save modifications
POST_CUSTOM_TYPE_PREFIX = "custom_"
POST_ME = "me"
PROPS_ADD_CHANNEL_MEMBER = "add_channel_member"
POST_PROPS_ADDED_USER_ID = "addedUserId"
POST_PROPS_DELETE_BY = "deleteBy"
POST_PROPS_OVERRIDE_ICON_URL = "override_icon_url"
POST_PROPS_OVERRIDE_ICON_EMOJI = "override_icon_emoji"
POST_PROPS_MENTION_HIGHLIGHT_DISABLED = "mentionHighlightDisabled"
POST_PROPS_GROUP_HIGHLIGHT_DISABLED = "disable_group_highlight"
)
var AT_MENTION_PATTEN = regexp.MustCompile(`\B@`)
type Post struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
EditAt int64 `json:"edit_at"`
DeleteAt int64 `json:"delete_at"`
IsPinned bool `json:"is_pinned"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
OriginalId string `json:"original_id"`
Message string `json:"message"`
// MessageSource will contain the message as submitted by the user if Message has been modified
// by Mattermost for presentation (e.g if an image proxy is being used). It should be used to
// populate edit boxes if present.
MessageSource string `json:"message_source,omitempty" db:"-"`
Type string `json:"type"`
propsMu sync.RWMutex `db:"-"` // Unexported mutex used to guard Post.Props.
Props StringInterface `json:"props"` // Deprecated: use GetProps()
Hashtags string `json:"hashtags"`
Filenames StringArray `json:"filenames,omitempty"` // Deprecated, do not use this field any more
FileIds StringArray `json:"file_ids,omitempty"`
PendingPostId string `json:"pending_post_id" db:"-"`
HasReactions bool `json:"has_reactions,omitempty"`
// Transient data populated before sending a post to the client
ReplyCount int64 `json:"reply_count" db:"-"`
Metadata *PostMetadata `json:"metadata,omitempty" db:"-"`
}
type PostEphemeral struct {
UserID string `json:"user_id"`
Post *Post `json:"post"`
}
type PostPatch struct {
IsPinned *bool `json:"is_pinned"`
Message *string `json:"message"`
Props *StringInterface `json:"props"`
FileIds *StringArray `json:"file_ids"`
HasReactions *bool `json:"has_reactions"`
}
type SearchParameter struct {
Terms *string `json:"terms"`
IsOrSearch *bool `json:"is_or_search"`
TimeZoneOffset *int `json:"time_zone_offset"`
Page *int `json:"page"`
PerPage *int `json:"per_page"`
IncludeDeletedChannels *bool `json:"include_deleted_channels"`
}
type AnalyticsPostCountsOptions struct {
TeamId string
BotsOnly bool
YesterdayOnly bool
}
func (o *PostPatch) WithRewrittenImageURLs(f func(string) string) *PostPatch {
copy := *o
if copy.Message != nil {
*copy.Message = RewriteImageURLs(*o.Message, f)
}
return &copy
}
type PostForExport struct {
Post
TeamName string
ChannelName string
Username string
ReplyCount int
}
type DirectPostForExport struct {
Post
User string
ChannelMembers *[]string
}
type ReplyForExport struct {
Post
Username string
}
type PostForIndexing struct {
Post
TeamId string `json:"team_id"`
ParentCreateAt *int64 `json:"parent_create_at"`
}
// 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 {
if dst == nil {
return errors.New("dst cannot be nil")
}
o.propsMu.RLock()
defer o.propsMu.RUnlock()
dst.propsMu.Lock()
defer dst.propsMu.Unlock()
dst.Id = o.Id
dst.CreateAt = o.CreateAt
dst.UpdateAt = o.UpdateAt
dst.EditAt = o.EditAt
dst.DeleteAt = o.DeleteAt
dst.IsPinned = o.IsPinned
dst.UserId = o.UserId
dst.ChannelId = o.ChannelId
dst.RootId = o.RootId
dst.ParentId = o.ParentId
dst.OriginalId = o.OriginalId
dst.Message = o.Message
dst.MessageSource = o.MessageSource
dst.Type = o.Type
dst.Props = o.Props
dst.Hashtags = o.Hashtags
dst.Filenames = o.Filenames
dst.FileIds = o.FileIds
dst.PendingPostId = o.PendingPostId
dst.HasReactions = o.HasReactions
dst.ReplyCount = o.ReplyCount
dst.Metadata = o.Metadata
return nil
}
// Clone shallowly copies the post and returns the copy.
func (o *Post) Clone() *Post {
copy := &Post{}
o.ShallowCopy(copy)
return copy
}
func (o *Post) ToJson() string {
copy := o.Clone()
copy.StripActionIntegrations()
b, _ := json.Marshal(copy)
return string(b)
}
func (o *Post) ToUnsanitizedJson() string {
b, _ := json.Marshal(o)
return string(b)
}
type GetPostsSinceOptions struct {
ChannelId string
Time int64
SkipFetchThreads bool
}
type GetPostsOptions struct {
ChannelId string
PostId string
Page int
PerPage int
SkipFetchThreads bool
}
func PostFromJson(data io.Reader) *Post {
var o *Post
json.NewDecoder(data).Decode(&o)
return o
}
func (o *Post) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
func (o *Post) IsValid(maxPostSize int) *AppError {
if !IsValidId(o.Id) {
return NewAppError("Post.IsValid", "model.post.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("Post.IsValid", "model.post.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("Post.IsValid", "model.post.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidId(o.UserId) {
return NewAppError("Post.IsValid", "model.post.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.ChannelId) {
return NewAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if !(IsValidId(o.RootId) || len(o.RootId) == 0) {
return NewAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "", http.StatusBadRequest)
}
if !(IsValidId(o.ParentId) || len(o.ParentId) == 0) {
return NewAppError("Post.IsValid", "model.post.is_valid.parent_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ParentId) == 26 && len(o.RootId) == 0 {
return NewAppError("Post.IsValid", "model.post.is_valid.root_parent.app_error", nil, "", http.StatusBadRequest)
}
if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) {
return NewAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "", http.StatusBadRequest)
}
if utf8.RuneCountInString(o.Message) > maxPostSize {
return NewAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.Hashtags) > POST_HASHTAGS_MAX_RUNES {
return NewAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
switch o.Type {
case
POST_DEFAULT,
POST_SYSTEM_GENERIC,
POST_JOIN_LEAVE,
POST_AUTO_RESPONDER,
POST_ADD_REMOVE,
POST_JOIN_CHANNEL,
POST_GUEST_JOIN_CHANNEL,
POST_LEAVE_CHANNEL,
POST_JOIN_TEAM,
POST_LEAVE_TEAM,
POST_ADD_TO_CHANNEL,
POST_ADD_GUEST_TO_CHANNEL,
POST_REMOVE_FROM_CHANNEL,
POST_MOVE_CHANNEL,
POST_ADD_TO_TEAM,
POST_REMOVE_FROM_TEAM,
POST_SLACK_ATTACHMENT,
POST_HEADER_CHANGE,
POST_PURPOSE_CHANGE,
POST_DISPLAYNAME_CHANGE,
POST_CONVERT_CHANNEL,
POST_CHANNEL_DELETED,
POST_CHANNEL_RESTORED,
POST_CHANGE_CHANNEL_PRIVACY,
POST_ME,
POST_ADD_BOT_TEAMS_CHANNELS:
default:
if !strings.HasPrefix(o.Type, POST_CUSTOM_TYPE_PREFIX) {
return NewAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type, http.StatusBadRequest)
}
}
if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > POST_FILENAMES_MAX_RUNES {
return NewAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(ArrayToJson(o.FileIds)) > POST_FILEIDS_MAX_RUNES {
return NewAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(StringInterfaceToJson(o.GetProps())) > POST_PROPS_MAX_RUNES {
return NewAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
return nil
}
func (o *Post) SanitizeProps() {
membersToSanitize := []string{
PROPS_ADD_CHANNEL_MEMBER,
}
for _, member := range membersToSanitize {
if _, ok := o.GetProps()[member]; ok {
o.DelProp(member)
}
}
}
func (o *Post) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.OriginalId = ""
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
}
o.UpdateAt = o.CreateAt
o.PreCommit()
}
func (o *Post) PreCommit() {
if o.GetProps() == nil {
o.SetProps(make(map[string]interface{}))
}
if o.Filenames == nil {
o.Filenames = []string{}
}
if o.FileIds == nil {
o.FileIds = []string{}
}
o.GenerateActionIds()
// There's a rare bug where the client sends up duplicate FileIds so protect against that
o.FileIds = RemoveDuplicateStrings(o.FileIds)
}
func (o *Post) MakeNonNil() {
if o.GetProps() == nil {
o.SetProps(make(map[string]interface{}))
}
}
func (o *Post) DelProp(key string) {
o.propsMu.Lock()
defer o.propsMu.Unlock()
propsCopy := make(map[string]interface{}, len(o.Props)-1)
for k, v := range o.Props {
propsCopy[k] = v
}
delete(propsCopy, key)
o.Props = propsCopy
}
func (o *Post) AddProp(key string, value interface{}) {
o.propsMu.Lock()
defer o.propsMu.Unlock()
propsCopy := make(map[string]interface{}, len(o.Props)+1)
for k, v := range o.Props {
propsCopy[k] = v
}
propsCopy[key] = value
o.Props = propsCopy
}
func (o *Post) GetProps() StringInterface {
o.propsMu.RLock()
defer o.propsMu.RUnlock()
return o.Props
}
func (o *Post) SetProps(props StringInterface) {
o.propsMu.Lock()
defer o.propsMu.Unlock()
o.Props = props
}
func (o *Post) GetProp(key string) interface{} {
o.propsMu.RLock()
defer o.propsMu.RUnlock()
return o.Props[key]
}
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
}
func (o *Post) IsJoinLeaveMessage() bool {
return o.Type == POST_JOIN_LEAVE ||
o.Type == POST_ADD_REMOVE ||
o.Type == POST_JOIN_CHANNEL ||
o.Type == POST_LEAVE_CHANNEL ||
o.Type == POST_JOIN_TEAM ||
o.Type == POST_LEAVE_TEAM ||
o.Type == POST_ADD_TO_CHANNEL ||
o.Type == POST_REMOVE_FROM_CHANNEL ||
o.Type == POST_ADD_TO_TEAM ||
o.Type == POST_REMOVE_FROM_TEAM
}
func (o *Post) Patch(patch *PostPatch) {
if patch.IsPinned != nil {
o.IsPinned = *patch.IsPinned
}
if patch.Message != nil {
o.Message = *patch.Message
}
if patch.Props != nil {
newProps := *patch.Props
o.SetProps(newProps)
}
if patch.FileIds != nil {
o.FileIds = *patch.FileIds
}
if patch.HasReactions != nil {
o.HasReactions = *patch.HasReactions
}
}
func (o *PostPatch) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func PostPatchFromJson(data io.Reader) *PostPatch {
decoder := json.NewDecoder(data)
var post PostPatch
err := decoder.Decode(&post)
if err != nil {
return nil
}
return &post
}
func (o *SearchParameter) SearchParameterToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func SearchParameterFromJson(data io.Reader) *SearchParameter {
decoder := json.NewDecoder(data)
var searchParam SearchParameter
err := decoder.Decode(&searchParam)
if err != nil {
return nil
}
return &searchParam
}
func (o *Post) ChannelMentions() []string {
return ChannelMentions(o.Message)
}
// DisableMentionHighlights disables a posts mention highlighting and returns the first channel mention that was present in the message.
func (o *Post) DisableMentionHighlights() string {
mention, hasMentions := findAtChannelMention(o.Message)
if hasMentions {
o.AddProp(POST_PROPS_MENTION_HIGHLIGHT_DISABLED, true)
}
return mention
}
// DisableMentionHighlights disables mention highlighting for a post patch if required.
func (o *PostPatch) DisableMentionHighlights() {
if _, hasMentions := findAtChannelMention(*o.Message); hasMentions {
if o.Props == nil {
o.Props = &StringInterface{}
}
(*o.Props)[POST_PROPS_MENTION_HIGHLIGHT_DISABLED] = true
}
}
func findAtChannelMention(message string) (mention string, found bool) {
re := regexp.MustCompile(`(?i)\B@(channel|all|here)\b`)
matched := re.FindStringSubmatch(message)
if found = (len(matched) > 0); found {
mention = strings.ToLower(matched[0])
}
return
}
func (o *Post) Attachments() []*SlackAttachment {
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
return attachments
}
var ret []*SlackAttachment
if attachments, ok := o.GetProp("attachments").([]interface{}); ok {
for _, attachment := range attachments {
if enc, err := json.Marshal(attachment); err == nil {
var decoded SlackAttachment
if json.Unmarshal(enc, &decoded) == nil {
ret = append(ret, &decoded)
}
}
}
}
return ret
}
func (o *Post) AttachmentsEqual(input *Post) bool {
attachments := o.Attachments()
inputAttachments := input.Attachments()
if len(attachments) != len(inputAttachments) {
return false
}
for i := range attachments {
if !attachments[i].Equals(inputAttachments[i]) {
return false
}
}
return true
}
var markdownDestinationEscaper = strings.NewReplacer(
`\`, `\\`,
`<`, `\<`,
`>`, `\>`,
`(`, `\(`,
`)`, `\)`,
)
// WithRewrittenImageURLs returns a new shallow copy of the post where the message has been
// rewritten via RewriteImageURLs.
func (o *Post) WithRewrittenImageURLs(f func(string) string) *Post {
copy := o.Clone()
copy.Message = RewriteImageURLs(o.Message, f)
if copy.MessageSource == "" && copy.Message != o.Message {
copy.MessageSource = o.Message
}
return copy
}
func (o *PostEphemeral) ToUnsanitizedJson() string {
b, _ := json.Marshal(o)
return string(b)
}
// RewriteImageURLs takes a message and returns a copy that has all of the image URLs replaced
// according to the function f. For each image URL, f will be invoked, and the resulting markdown
// will contain the URL returned by that invocation instead.
//
// Image URLs are destination URLs used in inline images or reference definitions that are used
// anywhere in the input markdown as an image.
func RewriteImageURLs(message string, f func(string) string) string {
if !strings.Contains(message, "![") {
return message
}
var ranges []markdown.Range
markdown.Inspect(message, func(blockOrInline interface{}) bool {
switch v := blockOrInline.(type) {
case *markdown.ReferenceImage:
ranges = append(ranges, v.ReferenceDefinition.RawDestination)
case *markdown.InlineImage:
ranges = append(ranges, v.RawDestination)
default:
return true
}
return true
})
if ranges == nil {
return message
}
sort.Slice(ranges, func(i, j int) bool {
return ranges[i].Position < ranges[j].Position
})
copyRanges := make([]markdown.Range, 0, len(ranges))
urls := make([]string, 0, len(ranges))
resultLength := len(message)
start := 0
for i, r := range ranges {
switch {
case i == 0:
case r.Position != ranges[i-1].Position:
start = ranges[i-1].End
default:
continue
}
original := message[r.Position:r.End]
replacement := markdownDestinationEscaper.Replace(f(markdown.Unescape(original)))
resultLength += len(replacement) - len(original)
copyRanges = append(copyRanges, markdown.Range{Position: start, End: r.Position})
urls = append(urls, replacement)
}
result := make([]byte, resultLength)
offset := 0
for i, r := range copyRanges {
offset += copy(result[offset:], message[r.Position:r.End])
offset += copy(result[offset:], urls[i])
}
copy(result[offset:], message[ranges[len(ranges)-1].End:])
return string(result)
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
const (
POST_EMBED_IMAGE PostEmbedType = "image"
POST_EMBED_MESSAGE_ATTACHMENT PostEmbedType = "message_attachment"
POST_EMBED_OPENGRAPH PostEmbedType = "opengraph"
POST_EMBED_LINK PostEmbedType = "link"
)
type PostEmbedType string
type PostEmbed struct {
Type PostEmbedType `json:"type"`
// The URL of the embedded content. Used for image and OpenGraph embeds.
URL string `json:"url,omitempty"`
// Any additional data for the embedded content. Only used for OpenGraph embeds.
Data interface{} `json:"data,omitempty"`
}

View File

@ -0,0 +1,166 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"sort"
)
type PostList struct {
Order []string `json:"order"`
Posts map[string]*Post `json:"posts"`
NextPostId string `json:"next_post_id"`
PrevPostId string `json:"prev_post_id"`
}
func NewPostList() *PostList {
return &PostList{
Order: make([]string, 0),
Posts: make(map[string]*Post),
NextPostId: "",
PrevPostId: "",
}
}
func (o *PostList) ToSlice() []*Post {
var posts []*Post
for _, id := range o.Order {
posts = append(posts, o.Posts[id])
}
return posts
}
func (o *PostList) WithRewrittenImageURLs(f func(string) string) *PostList {
copy := *o
copy.Posts = make(map[string]*Post)
for id, post := range o.Posts {
copy.Posts[id] = post.WithRewrittenImageURLs(f)
}
return &copy
}
func (o *PostList) StripActionIntegrations() {
posts := o.Posts
o.Posts = make(map[string]*Post)
for id, post := range posts {
pcopy := post.Clone()
pcopy.StripActionIntegrations()
o.Posts[id] = pcopy
}
}
func (o *PostList) ToJson() string {
copy := *o
copy.StripActionIntegrations()
b, err := json.Marshal(&copy)
if err != nil {
return ""
} else {
return string(b)
}
}
func (o *PostList) MakeNonNil() {
if o.Order == nil {
o.Order = make([]string, 0)
}
if o.Posts == nil {
o.Posts = make(map[string]*Post)
}
for _, v := range o.Posts {
v.MakeNonNil()
}
}
func (o *PostList) AddOrder(id string) {
if o.Order == nil {
o.Order = make([]string, 0, 128)
}
o.Order = append(o.Order, id)
}
func (o *PostList) AddPost(post *Post) {
if o.Posts == nil {
o.Posts = make(map[string]*Post)
}
o.Posts[post.Id] = post
}
func (o *PostList) UniqueOrder() {
keys := make(map[string]bool)
order := []string{}
for _, postId := range o.Order {
if _, value := keys[postId]; !value {
keys[postId] = true
order = append(order, postId)
}
}
o.Order = order
}
func (o *PostList) Extend(other *PostList) {
for postId := range other.Posts {
o.AddPost(other.Posts[postId])
}
for _, postId := range other.Order {
o.AddOrder(postId)
}
o.UniqueOrder()
}
func (o *PostList) SortByCreateAt() {
sort.Slice(o.Order, func(i, j int) bool {
return o.Posts[o.Order[i]].CreateAt > o.Posts[o.Order[j]].CreateAt
})
}
func (o *PostList) Etag() string {
id := "0"
var t int64 = 0
for _, v := range o.Posts {
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 (o *PostList) IsChannelId(channelId string) bool {
for _, v := range o.Posts {
if v.ChannelId != channelId {
return false
}
}
return true
}
func PostListFromJson(data io.Reader) *PostList {
var o *PostList
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,45 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
)
type PostMetadata struct {
// Embeds holds information required to render content embedded in the post. This includes the OpenGraph metadata
// for links in the post.
Embeds []*PostEmbed `json:"embeds,omitempty"`
// Emojis holds all custom emojis used in the post or used in reaction to the post.
Emojis []*Emoji `json:"emojis,omitempty"`
// Files holds information about the file attachments on the post.
Files []*FileInfo `json:"files,omitempty"`
// Images holds the dimensions of all external images in the post as a map of the image URL to its diemsnions.
// This includes image embeds (when the message contains a plaintext link to an image), Markdown images, images
// contained in the OpenGraph metadata, and images contained in message attachments. It does not contain
// the dimensions of any file attachments as those are stored in FileInfos.
Images map[string]*PostImage `json:"images,omitempty"`
// Reactions holds reactions made to the post.
Reactions []*Reaction `json:"reactions,omitempty"`
}
type PostImage struct {
Width int `json:"width"`
Height int `json:"height"`
// Format is the name of the image format as used by image/go such as "png", "gif", or "jpeg".
Format string `json:"format"`
// FrameCount stores the number of frames in this image, if it is an animated gif. It will be 0 for other formats.
FrameCount int `json:"frame_count"`
}
func (o *PostImage) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type PostSearchMatches map[string][]string
type PostSearchResults struct {
*PostList
Matches PostSearchMatches `json:"matches"`
}
func MakePostSearchResults(posts *PostList, matches PostSearchMatches) *PostSearchResults {
return &PostSearchResults{
posts,
matches,
}
}
func (o *PostSearchResults) ToJson() string {
copy := *o
copy.PostList.StripActionIntegrations()
b, err := json.Marshal(&copy)
if err != nil {
return ""
} else {
return string(b)
}
}
func PostSearchResultsFromJson(data io.Reader) *PostSearchResults {
var o *PostSearchResults
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,123 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"regexp"
"strings"
"unicode/utf8"
)
const (
PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show"
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post"
PREFERENCE_CATEGORY_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_THEME = "theme"
// the name for theme props is the team id
PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app"
// the name for oauth_app is the client_id and value is the current scope
PREFERENCE_CATEGORY_LAST = "last"
PREFERENCE_NAME_LAST_CHANNEL = "channel"
PREFERENCE_NAME_LAST_TEAM = "team"
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS = "30" // the "immediate" setting is actually 30s
PREFERENCE_EMAIL_INTERVAL_BATCHING_SECONDS = "900" // fifteen minutes is 900 seconds
PREFERENCE_EMAIL_INTERVAL_IMMEDIATELY = "immediately"
PREFERENCE_EMAIL_INTERVAL_FIFTEEN = "fifteen"
PREFERENCE_EMAIL_INTERVAL_FIFTEEN_AS_SECONDS = "900"
PREFERENCE_EMAIL_INTERVAL_HOUR = "hour"
PREFERENCE_EMAIL_INTERVAL_HOUR_AS_SECONDS = "3600"
)
type Preference struct {
UserId string `json:"user_id"`
Category string `json:"category"`
Name string `json:"name"`
Value string `json:"value"`
}
func (o *Preference) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func PreferenceFromJson(data io.Reader) *Preference {
var o *Preference
json.NewDecoder(data).Decode(&o)
return o
}
func (o *Preference) IsValid() *AppError {
if !IsValidId(o.UserId) {
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 {
return NewAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category, http.StatusBadRequest)
}
if len(o.Name) > 32 {
return NewAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.Value) > 2000 {
return NewAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value, http.StatusBadRequest)
}
if o.Category == PREFERENCE_CATEGORY_THEME {
var unused map[string]string
if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil {
return NewAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value, http.StatusBadRequest)
}
}
return nil
}
func (o *Preference) PreUpdate() {
if o.Category == PREFERENCE_CATEGORY_THEME {
// decode the value of theme (a map of strings to string) and eliminate any invalid values
var props map[string]string
if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil {
// just continue, the invalid preference value should get caught by IsValid before saving
return
}
colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`)
// blank out any invalid theme values
for name, value := range props {
if name == "image" || name == "type" || name == "codeTheme" {
continue
}
if !colorPattern.MatchString(value) {
props[name] = "#ffffff"
}
}
if b, err := json.Marshal(props); err == nil {
o.Value = string(b)
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type Preferences []Preference
func (o *Preferences) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
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 {
return nil, err
}
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"errors"
"io"
"strings"
)
const (
PUSH_NOTIFY_APPLE = "apple"
PUSH_NOTIFY_ANDROID = "android"
PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn"
PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn"
PUSH_TYPE_MESSAGE = "message"
PUSH_TYPE_CLEAR = "clear"
PUSH_TYPE_UPDATE_BADGE = "update_badge"
PUSH_MESSAGE_V2 = "v2"
PUSH_SOUND_NONE = "none"
// The category is set to handle a set of interactive Actions
// with the push notifications
CATEGORY_CAN_REPLY = "CAN_REPLY"
MHPNS = "https://push.mattermost.com"
PUSH_SEND_PREPARE = "Prepared to send"
PUSH_SEND_SUCCESS = "Successful"
PUSH_NOT_SENT = "Not Sent due to preferences"
PUSH_RECEIVED = "Received by device"
)
type PushNotificationAck struct {
Id string `json:"id"`
ClientReceivedAt int64 `json:"received_at"`
ClientPlatform string `json:"platform"`
NotificationType string `json:"type"`
PostId string `json:"post_id,omitempty"`
IsIdLoaded bool `json:"is_id_loaded"`
}
type PushNotification struct {
AckId string `json:"ack_id"`
Platform string `json:"platform"`
ServerId string `json:"server_id"`
DeviceId string `json:"device_id"`
PostId string `json:"post_id"`
Category string `json:"category,omitempty"`
Sound string `json:"sound,omitempty"`
Message string `json:"message,omitempty"`
Badge int `json:"badge,omitempty"`
ContentAvailable int `json:"cont_ava,omitempty"`
TeamId string `json:"team_id,omitempty"`
ChannelId string `json:"channel_id,omitempty"`
RootId string `json:"root_id,omitempty"`
ChannelName string `json:"channel_name,omitempty"`
Type string `json:"type,omitempty"`
SenderId string `json:"sender_id,omitempty"`
SenderName string `json:"sender_name,omitempty"`
OverrideUsername string `json:"override_username,omitempty"`
OverrideIconUrl string `json:"override_icon_url,omitempty"`
FromWebhook string `json:"from_webhook,omitempty"`
Version string `json:"version,omitempty"`
IsIdLoaded bool `json:"is_id_loaded"`
}
func (me *PushNotification) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func (me *PushNotification) DeepCopy() *PushNotification {
copy := *me
return &copy
}
func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) {
index := strings.Index(deviceId, ":")
if index > -1 {
me.Platform = deviceId[:index]
me.DeviceId = deviceId[index+1:]
}
}
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 {
return nil, err
}
return me, nil
}
func PushNotificationAckFromJson(data io.Reader) (*PushNotificationAck, error) {
if data == nil {
return nil, errors.New("push notification data can't be nil")
}
var ack *PushNotificationAck
if err := json.NewDecoder(data).Decode(&ack); err != nil {
return nil, err
}
return ack, nil
}
func (ack *PushNotificationAck) ToJson() string {
b, _ := json.Marshal(ack)
return string(b)
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
PUSH_STATUS = "status"
PUSH_STATUS_OK = "OK"
PUSH_STATUS_FAIL = "FAIL"
PUSH_STATUS_REMOVE = "REMOVE"
PUSH_STATUS_ERROR_MSG = "error"
)
type PushResponse map[string]string
func NewOkPushResponse() PushResponse {
m := make(map[string]string)
m[PUSH_STATUS] = PUSH_STATUS_OK
return m
}
func NewRemovePushResponse() PushResponse {
m := make(map[string]string)
m[PUSH_STATUS] = PUSH_STATUS_REMOVE
return m
}
func NewErrorPushResponse(message string) PushResponse {
m := make(map[string]string)
m[PUSH_STATUS] = PUSH_STATUS_FAIL
m[PUSH_STATUS_ERROR_MSG] = message
return m
}
func (me *PushResponse) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func PushResponseFromJson(data io.Reader) PushResponse {
decoder := json.NewDecoder(data)
var objmap PushResponse
if err := decoder.Decode(&objmap); err != nil {
return make(map[string]string)
} else {
return objmap
}
}

View File

@ -0,0 +1,92 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
"regexp"
)
type Reaction struct {
UserId string `json:"user_id"`
PostId string `json:"post_id"`
EmojiName string `json:"emoji_name"`
CreateAt int64 `json:"create_at"`
}
func (o *Reaction) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func ReactionFromJson(data io.Reader) *Reaction {
var o Reaction
if err := json.NewDecoder(data).Decode(&o); err != nil {
return nil
} else {
return &o
}
}
func ReactionsToJson(o []*Reaction) string {
b, _ := json.Marshal(o)
return string(b)
}
func MapPostIdToReactionsToJson(o map[string][]*Reaction) string {
b, _ := json.Marshal(o)
return string(b)
}
func MapPostIdToReactionsFromJson(data io.Reader) map[string][]*Reaction {
decoder := json.NewDecoder(data)
var objmap map[string][]*Reaction
if err := decoder.Decode(&objmap); err != nil {
return make(map[string][]*Reaction)
} else {
return objmap
}
}
func ReactionsFromJson(data io.Reader) []*Reaction {
var o []*Reaction
if err := json.NewDecoder(data).Decode(&o); err != nil {
return nil
} else {
return o
}
}
func (o *Reaction) IsValid() *AppError {
if !IsValidId(o.UserId) {
return NewAppError("Reaction.IsValid", "model.reaction.is_valid.user_id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest)
}
if !IsValidId(o.PostId) {
return NewAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId, http.StatusBadRequest)
}
validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`)
if len(o.EmojiName) == 0 || 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)
}
if o.CreateAt == 0 {
return NewAppError("Reaction.IsValid", "model.reaction.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *Reaction) PreSave() {
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
}
}

View File

@ -0,0 +1,632 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"strings"
)
var BuiltInSchemeManagedRoleIDs []string
func init() {
BuiltInSchemeManagedRoleIDs = []string{
SYSTEM_GUEST_ROLE_ID,
SYSTEM_USER_ROLE_ID,
SYSTEM_ADMIN_ROLE_ID,
SYSTEM_POST_ALL_ROLE_ID,
SYSTEM_POST_ALL_PUBLIC_ROLE_ID,
SYSTEM_USER_ACCESS_TOKEN_ROLE_ID,
TEAM_GUEST_ROLE_ID,
TEAM_USER_ROLE_ID,
TEAM_ADMIN_ROLE_ID,
TEAM_POST_ALL_ROLE_ID,
TEAM_POST_ALL_PUBLIC_ROLE_ID,
CHANNEL_GUEST_ROLE_ID,
CHANNEL_USER_ROLE_ID,
CHANNEL_ADMIN_ROLE_ID,
}
}
type RoleType string
type RoleScope string
const (
SYSTEM_GUEST_ROLE_ID = "system_guest"
SYSTEM_USER_ROLE_ID = "system_user"
SYSTEM_ADMIN_ROLE_ID = "system_admin"
SYSTEM_POST_ALL_ROLE_ID = "system_post_all"
SYSTEM_POST_ALL_PUBLIC_ROLE_ID = "system_post_all_public"
SYSTEM_USER_ACCESS_TOKEN_ROLE_ID = "system_user_access_token"
TEAM_GUEST_ROLE_ID = "team_guest"
TEAM_USER_ROLE_ID = "team_user"
TEAM_ADMIN_ROLE_ID = "team_admin"
TEAM_POST_ALL_ROLE_ID = "team_post_all"
TEAM_POST_ALL_PUBLIC_ROLE_ID = "team_post_all_public"
CHANNEL_GUEST_ROLE_ID = "channel_guest"
CHANNEL_USER_ROLE_ID = "channel_user"
CHANNEL_ADMIN_ROLE_ID = "channel_admin"
ROLE_NAME_MAX_LENGTH = 64
ROLE_DISPLAY_NAME_MAX_LENGTH = 128
ROLE_DESCRIPTION_MAX_LENGTH = 1024
RoleScopeSystem RoleScope = "System"
RoleScopeTeam RoleScope = "Team"
RoleScopeChannel RoleScope = "Channel"
RoleTypeGuest RoleType = "Guest"
RoleTypeUser RoleType = "User"
RoleTypeAdmin RoleType = "Admin"
)
type Role struct {
Id string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
Permissions []string `json:"permissions"`
SchemeManaged bool `json:"scheme_managed"`
BuiltIn bool `json:"built_in"`
}
type RolePatch struct {
Permissions *[]string `json:"permissions"`
}
type RolePermissions struct {
RoleID string
Permissions []string
}
func (r *Role) ToJson() string {
b, _ := json.Marshal(r)
return string(b)
}
func RoleFromJson(data io.Reader) *Role {
var r *Role
json.NewDecoder(data).Decode(&r)
return r
}
func RoleListToJson(r []*Role) string {
b, _ := json.Marshal(r)
return string(b)
}
func RoleListFromJson(data io.Reader) []*Role {
var roles []*Role
json.NewDecoder(data).Decode(&roles)
return roles
}
func (r *RolePatch) ToJson() string {
b, _ := json.Marshal(r)
return string(b)
}
func RolePatchFromJson(data io.Reader) *RolePatch {
var rolePatch *RolePatch
json.NewDecoder(data).Decode(&rolePatch)
return rolePatch
}
func (r *Role) Patch(patch *RolePatch) {
if patch.Permissions != nil {
r.Permissions = *patch.Permissions
}
}
// MergeChannelHigherScopedPermissions is meant to be invoked on a channel scheme's role and merges the higher-scoped
// channel role's permissions.
func (r *Role) MergeChannelHigherScopedPermissions(higherScopedPermissions *RolePermissions) {
mergedPermissions := []string{}
higherScopedPermissionsMap := AsStringBoolMap(higherScopedPermissions.Permissions)
rolePermissionsMap := AsStringBoolMap(r.Permissions)
for _, cp := range ALL_PERMISSIONS {
if cp.Scope != PERMISSION_SCOPE_CHANNEL {
continue
}
_, presentOnHigherScope := higherScopedPermissionsMap[cp.Id]
// For the channel admin role always look to the higher scope to determine if the role has ther 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 {
mergedPermissions = append(mergedPermissions, cp.Id)
continue
}
_, permissionIsModerated := CHANNEL_MODERATED_PERMISSIONS_MAP[cp.Id]
if permissionIsModerated {
_, presentOnRole := rolePermissionsMap[cp.Id]
if presentOnRole && presentOnHigherScope {
mergedPermissions = append(mergedPermissions, cp.Id)
}
} else {
if presentOnHigherScope {
mergedPermissions = append(mergedPermissions, cp.Id)
}
}
}
r.Permissions = mergedPermissions
}
// Returns an array of permissions that are in either role.Permissions
// or patch.Permissions, but not both.
func PermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
var result []string
if patch.Permissions == nil {
return result
}
roleMap := make(map[string]bool)
patchMap := make(map[string]bool)
for _, permission := range role.Permissions {
roleMap[permission] = true
}
for _, permission := range *patch.Permissions {
patchMap[permission] = true
}
for _, permission := range role.Permissions {
if !patchMap[permission] {
result = append(result, permission)
}
}
for _, permission := range *patch.Permissions {
if !roleMap[permission] {
result = append(result, permission)
}
}
return result
}
func ChannelModeratedPermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
var result []string
if role == nil {
return result
}
if patch.Permissions == nil {
return result
}
roleMap := make(map[string]bool)
patchMap := make(map[string]bool)
for _, permission := range role.Permissions {
if channelModeratedPermissionName, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; found {
roleMap[channelModeratedPermissionName] = true
}
}
for _, permission := range *patch.Permissions {
if channelModeratedPermissionName, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; found {
patchMap[channelModeratedPermissionName] = true
}
}
for permissionKey := range roleMap {
if !patchMap[permissionKey] {
result = append(result, permissionKey)
}
}
for permissionKey := range patchMap {
if !roleMap[permissionKey] {
result = append(result, permissionKey)
}
}
return result
}
// GetChannelModeratedPermissions returns a map of channel moderated permissions that the role has access to
func (r *Role) GetChannelModeratedPermissions(channelType string) map[string]bool {
moderatedPermissions := make(map[string]bool)
for _, permission := range r.Permissions {
if _, found := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; !found {
continue
}
for moderated, moderatedPermissionValue := range CHANNEL_MODERATED_PERMISSIONS_MAP {
// the moderated permission has already been found to be true so skip this iteration
if moderatedPermissions[moderatedPermissionValue] {
continue
}
if moderated == permission {
// Special case where the channel moderated permission for `manage_members` is different depending on whether the channel is private or public
if moderated == PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id || moderated == PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id {
canManagePublic := channelType == CHANNEL_OPEN && moderated == PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id
canManagePrivate := channelType == CHANNEL_PRIVATE && moderated == PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id
moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
} else {
moderatedPermissions[moderatedPermissionValue] = true
}
}
}
}
return moderatedPermissions
}
// RolePatchFromChannelModerationsPatch Creates and returns a RolePatch based on a slice of ChannelModerationPatchs, roleName is expected to be either "members" or "guests".
func (r *Role) RolePatchFromChannelModerationsPatch(channelModerationsPatch []*ChannelModerationPatch, roleName string) *RolePatch {
permissionsToAddToPatch := make(map[string]bool)
// Iterate through the list of existing permissions on the role and append permissions that we want to keep.
for _, permission := range r.Permissions {
// Permission is not moderated so dont add it to the patch and skip the channelModerationsPatch
if _, isModerated := CHANNEL_MODERATED_PERMISSIONS_MAP[permission]; !isModerated {
continue
}
permissionEnabled := true
// Check if permission has a matching moderated permission name inside the channel moderation patch
for _, channelModerationPatch := range channelModerationsPatch {
if *channelModerationPatch.Name == CHANNEL_MODERATED_PERMISSIONS_MAP[permission] {
// Permission key exists in patch with a value of false so skip over it
if roleName == "members" {
if channelModerationPatch.Roles.Members != nil && !*channelModerationPatch.Roles.Members {
permissionEnabled = false
}
} else if roleName == "guests" {
if channelModerationPatch.Roles.Guests != nil && !*channelModerationPatch.Roles.Guests {
permissionEnabled = false
}
}
}
}
if permissionEnabled {
permissionsToAddToPatch[permission] = true
}
}
// Iterate through the patch and add any permissions that dont already exist on the role
for _, channelModerationPatch := range channelModerationsPatch {
for permission, moderatedPermissionName := range CHANNEL_MODERATED_PERMISSIONS_MAP {
if roleName == "members" && channelModerationPatch.Roles.Members != nil && *channelModerationPatch.Roles.Members && *channelModerationPatch.Name == moderatedPermissionName {
permissionsToAddToPatch[permission] = true
}
if roleName == "guests" && channelModerationPatch.Roles.Guests != nil && *channelModerationPatch.Roles.Guests && *channelModerationPatch.Name == moderatedPermissionName {
permissionsToAddToPatch[permission] = true
}
}
}
patchPermissions := make([]string, 0, len(permissionsToAddToPatch))
for permission := range permissionsToAddToPatch {
patchPermissions = append(patchPermissions, permission)
}
return &RolePatch{Permissions: &patchPermissions}
}
func (r *Role) IsValid() bool {
if !IsValidId(r.Id) {
return false
}
return r.IsValidWithoutId()
}
func (r *Role) IsValidWithoutId() bool {
if !IsValidRoleName(r.Name) {
return false
}
if len(r.DisplayName) == 0 || len(r.DisplayName) > ROLE_DISPLAY_NAME_MAX_LENGTH {
return false
}
if len(r.Description) > ROLE_DESCRIPTION_MAX_LENGTH {
return false
}
for _, permission := range r.Permissions {
permissionValidated := false
for _, p := range ALL_PERMISSIONS {
if permission == p.Id {
permissionValidated = true
break
}
}
if !permissionValidated {
return false
}
}
return true
}
func IsValidRoleName(roleName string) bool {
if len(roleName) <= 0 || len(roleName) > ROLE_NAME_MAX_LENGTH {
return false
}
if strings.TrimLeft(roleName, "abcdefghijklmnopqrstuvwxyz0123456789_") != "" {
return false
}
return true
}
func MakeDefaultRoles() map[string]*Role {
roles := make(map[string]*Role)
roles[CHANNEL_GUEST_ROLE_ID] = &Role{
Name: "channel_guest",
DisplayName: "authentication.roles.channel_guest.name",
Description: "authentication.roles.channel_guest.description",
Permissions: []string{
PERMISSION_READ_CHANNEL.Id,
PERMISSION_ADD_REACTION.Id,
PERMISSION_REMOVE_REACTION.Id,
PERMISSION_UPLOAD_FILE.Id,
PERMISSION_EDIT_POST.Id,
PERMISSION_CREATE_POST.Id,
PERMISSION_USE_CHANNEL_MENTIONS.Id,
PERMISSION_USE_SLASH_COMMANDS.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[CHANNEL_USER_ROLE_ID] = &Role{
Name: "channel_user",
DisplayName: "authentication.roles.channel_user.name",
Description: "authentication.roles.channel_user.description",
Permissions: []string{
PERMISSION_READ_CHANNEL.Id,
PERMISSION_ADD_REACTION.Id,
PERMISSION_REMOVE_REACTION.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
PERMISSION_UPLOAD_FILE.Id,
PERMISSION_GET_PUBLIC_LINK.Id,
PERMISSION_CREATE_POST.Id,
PERMISSION_USE_CHANNEL_MENTIONS.Id,
PERMISSION_USE_SLASH_COMMANDS.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[CHANNEL_ADMIN_ROLE_ID] = &Role{
Name: "channel_admin",
DisplayName: "authentication.roles.channel_admin.name",
Description: "authentication.roles.channel_admin.description",
Permissions: []string{
PERMISSION_MANAGE_CHANNEL_ROLES.Id,
PERMISSION_USE_GROUP_MENTIONS.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[TEAM_GUEST_ROLE_ID] = &Role{
Name: "team_guest",
DisplayName: "authentication.roles.team_guest.name",
Description: "authentication.roles.team_guest.description",
Permissions: []string{
PERMISSION_VIEW_TEAM.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[TEAM_USER_ROLE_ID] = &Role{
Name: "team_user",
DisplayName: "authentication.roles.team_user.name",
Description: "authentication.roles.team_user.description",
Permissions: []string{
PERMISSION_LIST_TEAM_CHANNELS.Id,
PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
PERMISSION_READ_PUBLIC_CHANNEL.Id,
PERMISSION_VIEW_TEAM.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[TEAM_POST_ALL_ROLE_ID] = &Role{
Name: "team_post_all",
DisplayName: "authentication.roles.team_post_all.name",
Description: "authentication.roles.team_post_all.description",
Permissions: []string{
PERMISSION_CREATE_POST.Id,
PERMISSION_USE_CHANNEL_MENTIONS.Id,
},
SchemeManaged: false,
BuiltIn: true,
}
roles[TEAM_POST_ALL_PUBLIC_ROLE_ID] = &Role{
Name: "team_post_all_public",
DisplayName: "authentication.roles.team_post_all_public.name",
Description: "authentication.roles.team_post_all_public.description",
Permissions: []string{
PERMISSION_CREATE_POST_PUBLIC.Id,
PERMISSION_USE_CHANNEL_MENTIONS.Id,
},
SchemeManaged: false,
BuiltIn: true,
}
roles[TEAM_ADMIN_ROLE_ID] = &Role{
Name: "team_admin",
DisplayName: "authentication.roles.team_admin.name",
Description: "authentication.roles.team_admin.description",
Permissions: []string{
PERMISSION_REMOVE_USER_FROM_TEAM.Id,
PERMISSION_MANAGE_TEAM.Id,
PERMISSION_IMPORT_TEAM.Id,
PERMISSION_MANAGE_TEAM_ROLES.Id,
PERMISSION_MANAGE_CHANNEL_ROLES.Id,
PERMISSION_MANAGE_OTHERS_INCOMING_WEBHOOKS.Id,
PERMISSION_MANAGE_OTHERS_OUTGOING_WEBHOOKS.Id,
PERMISSION_MANAGE_SLASH_COMMANDS.Id,
PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id,
PERMISSION_MANAGE_INCOMING_WEBHOOKS.Id,
PERMISSION_MANAGE_OUTGOING_WEBHOOKS.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[SYSTEM_GUEST_ROLE_ID] = &Role{
Name: "system_guest",
DisplayName: "authentication.roles.global_guest.name",
Description: "authentication.roles.global_guest.description",
Permissions: []string{
PERMISSION_CREATE_DIRECT_CHANNEL.Id,
PERMISSION_CREATE_GROUP_CHANNEL.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[SYSTEM_USER_ROLE_ID] = &Role{
Name: "system_user",
DisplayName: "authentication.roles.global_user.name",
Description: "authentication.roles.global_user.description",
Permissions: []string{
PERMISSION_LIST_PUBLIC_TEAMS.Id,
PERMISSION_JOIN_PUBLIC_TEAMS.Id,
PERMISSION_CREATE_DIRECT_CHANNEL.Id,
PERMISSION_CREATE_GROUP_CHANNEL.Id,
PERMISSION_VIEW_MEMBERS.Id,
},
SchemeManaged: true,
BuiltIn: true,
}
roles[SYSTEM_POST_ALL_ROLE_ID] = &Role{
Name: "system_post_all",
DisplayName: "authentication.roles.system_post_all.name",
Description: "authentication.roles.system_post_all.description",
Permissions: []string{
PERMISSION_CREATE_POST.Id,
PERMISSION_USE_CHANNEL_MENTIONS.Id,
},
SchemeManaged: false,
BuiltIn: true,
}
roles[SYSTEM_POST_ALL_PUBLIC_ROLE_ID] = &Role{
Name: "system_post_all_public",
DisplayName: "authentication.roles.system_post_all_public.name",
Description: "authentication.roles.system_post_all_public.description",
Permissions: []string{
PERMISSION_CREATE_POST_PUBLIC.Id,
PERMISSION_USE_CHANNEL_MENTIONS.Id,
},
SchemeManaged: false,
BuiltIn: true,
}
roles[SYSTEM_USER_ACCESS_TOKEN_ROLE_ID] = &Role{
Name: "system_user_access_token",
DisplayName: "authentication.roles.system_user_access_token.name",
Description: "authentication.roles.system_user_access_token.description",
Permissions: []string{
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
},
SchemeManaged: false,
BuiltIn: true,
}
roles[SYSTEM_ADMIN_ROLE_ID] = &Role{
Name: "system_admin",
DisplayName: "authentication.roles.global_admin.name",
Description: "authentication.roles.global_admin.description",
// System admins can do anything channel and team admins can do
// plus everything members of teams and channels can do to all teams
// and channels on the system
Permissions: append(
append(
append(
append(
[]string{
PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id,
PERMISSION_MANAGE_SYSTEM.Id,
PERMISSION_MANAGE_ROLES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id,
PERMISSION_DELETE_PUBLIC_CHANNEL.Id,
PERMISSION_CREATE_PUBLIC_CHANNEL.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id,
PERMISSION_DELETE_PRIVATE_CHANNEL.Id,
PERMISSION_CREATE_PRIVATE_CHANNEL.Id,
PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id,
PERMISSION_MANAGE_OTHERS_INCOMING_WEBHOOKS.Id,
PERMISSION_MANAGE_OTHERS_OUTGOING_WEBHOOKS.Id,
PERMISSION_EDIT_OTHER_USERS.Id,
PERMISSION_EDIT_OTHERS_POSTS.Id,
PERMISSION_MANAGE_OAUTH.Id,
PERMISSION_INVITE_USER.Id,
PERMISSION_INVITE_GUEST.Id,
PERMISSION_PROMOTE_GUEST.Id,
PERMISSION_DEMOTE_TO_GUEST.Id,
PERMISSION_DELETE_POST.Id,
PERMISSION_DELETE_OTHERS_POSTS.Id,
PERMISSION_CREATE_TEAM.Id,
PERMISSION_ADD_USER_TO_TEAM.Id,
PERMISSION_LIST_USERS_WITHOUT_TEAM.Id,
PERMISSION_MANAGE_JOBS.Id,
PERMISSION_CREATE_POST_PUBLIC.Id,
PERMISSION_CREATE_POST_EPHEMERAL.Id,
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
PERMISSION_CREATE_BOT.Id,
PERMISSION_READ_BOTS.Id,
PERMISSION_READ_OTHERS_BOTS.Id,
PERMISSION_MANAGE_BOTS.Id,
PERMISSION_MANAGE_OTHERS_BOTS.Id,
PERMISSION_REMOVE_OTHERS_REACTIONS.Id,
PERMISSION_LIST_PRIVATE_TEAMS.Id,
PERMISSION_JOIN_PRIVATE_TEAMS.Id,
PERMISSION_VIEW_MEMBERS.Id,
},
roles[TEAM_USER_ROLE_ID].Permissions...,
),
roles[CHANNEL_USER_ROLE_ID].Permissions...,
),
roles[TEAM_ADMIN_ROLE_ID].Permissions...,
),
roles[CHANNEL_ADMIN_ROLE_ID].Permissions...,
),
SchemeManaged: true,
BuiltIn: true,
}
return roles
}

View File

@ -0,0 +1,199 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"encoding/xml"
"io"
"time"
)
const (
USER_AUTH_SERVICE_SAML = "saml"
USER_AUTH_SERVICE_SAML_TEXT = "SAML"
USER_AUTH_SERVICE_IS_SAML = "isSaml"
USER_AUTH_SERVICE_IS_MOBILE = "isMobile"
)
type SamlAuthRequest struct {
Base64AuthRequest string
URL string
RelayState string
}
type SamlCertificateStatus struct {
IdpCertificateFile bool `json:"idp_certificate_file"`
PrivateKeyFile bool `json:"private_key_file"`
PublicCertificateFile bool `json:"public_certificate_file"`
}
type SamlMetadataResponse struct {
IdpDescriptorUrl string `json:"idp_descriptor_url"`
IdpUrl string `json:"idp_url"`
IdpPublicCertificate string `json:"idp_public_certificate"`
}
type NameIDFormat struct {
XMLName xml.Name
Format string `xml:",attr,omitempty"`
Value string `xml:",innerxml"`
}
type NameID struct {
NameQualifier string `xml:",attr"`
SPNameQualifier string `xml:",attr"`
Format string `xml:",attr,omitempty"`
SPProvidedID string `xml:",attr"`
Value string `xml:",chardata"`
}
type AttributeValue struct {
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
Value string `xml:",chardata"`
NameID *NameID
}
type Attribute struct {
XMLName xml.Name
FriendlyName string `xml:",attr"`
Name string `xml:",attr"`
NameFormat string `xml:",attr"`
Values []AttributeValue `xml:"AttributeValue"`
}
type Endpoint struct {
XMLName xml.Name
Binding string `xml:"Binding,attr"`
Location string `xml:"Location,attr"`
ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
}
type IndexedEndpoint struct {
XMLName xml.Name
Binding string `xml:"Binding,attr"`
Location string `xml:"Location,attr"`
ResponseLocation *string `xml:"ResponseLocation,attr,omitempty"`
Index int `xml:"index,attr"`
IsDefault *bool `xml:"isDefault,attr"`
}
type IDPSSODescriptor struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
SSODescriptor
WantAuthnRequestsSigned *bool `xml:",attr"`
SingleSignOnServices []Endpoint `xml:"SingleSignOnService"`
NameIDMappingServices []Endpoint `xml:"NameIDMappingService"`
AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
AttributeProfiles []string `xml:"AttributeProfile"`
Attributes []Attribute `xml:"Attribute"`
}
type SSODescriptor struct {
XMLName xml.Name
RoleDescriptor
ArtifactResolutionServices []IndexedEndpoint `xml:"ArtifactResolutionService"`
SingleLogoutServices []Endpoint `xml:"SingleLogoutService"`
ManageNameIDServices []Endpoint `xml:"ManageNameIDService"`
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
}
type X509Certificate struct {
XMLName xml.Name
Cert string `xml:",innerxml"`
}
type X509Data struct {
XMLName xml.Name
X509Certificate X509Certificate `xml:"X509Certificate"`
}
type KeyInfo struct {
XMLName xml.Name
DS string `xml:"xmlns:ds,attr"`
X509Data X509Data `xml:"X509Data"`
}
type EncryptionMethod struct {
Algorithm string `xml:"Algorithm,attr"`
}
type KeyDescriptor struct {
XMLName xml.Name
Use string `xml:"use,attr,omitempty"`
KeyInfo KeyInfo `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo,omitempty"`
}
type RoleDescriptor struct {
XMLName xml.Name
ID string `xml:",attr,omitempty"`
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
ErrorURL string `xml:"errorURL,attr,omitempty"`
KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor,omitempty"`
Organization *Organization `xml:"Organization,omitempty"`
ContactPersons []ContactPerson `xml:"ContactPerson,omitempty"`
}
type ContactPerson struct {
XMLName xml.Name
ContactType string `xml:"contactType,attr"`
Company string
GivenName string
SurName string
EmailAddresses []string `xml:"EmailAddress"`
TelephoneNumbers []string `xml:"TelephoneNumber"`
}
type LocalizedName struct {
Lang string `xml:"xml lang,attr"`
Value string `xml:",chardata"`
}
type LocalizedURI struct {
Lang string `xml:"xml lang,attr"`
Value string `xml:",chardata"`
}
type Organization struct {
XMLName xml.Name
OrganizationNames []LocalizedName `xml:"OrganizationName"`
OrganizationDisplayNames []LocalizedName `xml:"OrganizationDisplayName"`
OrganizationURLs []LocalizedURI `xml:"OrganizationURL"`
}
type EntityDescriptor struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
EntityID string `xml:"entityID,attr"`
ID string `xml:",attr,omitempty"`
ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
RoleDescriptors []RoleDescriptor `xml:"RoleDescriptor"`
IDPSSODescriptors []IDPSSODescriptor `xml:"IDPSSODescriptor"`
Organization Organization `xml:"Organization"`
ContactPerson ContactPerson `xml:"ContactPerson"`
}
func (s *SamlCertificateStatus) ToJson() string {
b, _ := json.Marshal(s)
return string(b)
}
func SamlCertificateStatusFromJson(data io.Reader) *SamlCertificateStatus {
var status *SamlCertificateStatus
json.NewDecoder(data).Decode(&status)
return status
}
func (s *SamlMetadataResponse) ToJson() string {
b, _ := json.Marshal(s)
return string(b)
}
func SamlMetadataResponseFromJson(data io.Reader) *SamlMetadataResponse {
var status *SamlMetadataResponse
json.NewDecoder(data).Decode(&status)
return status
}

View File

@ -0,0 +1,77 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"fmt"
"time"
)
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{}
}
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
return createTask(name, function, timeToExecution, false)
}
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
return createTask(name, function, interval, true)
}
func createTask(name string, function TaskFunc, interval time.Duration, recurring bool) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: interval,
Recurring: recurring,
function: function,
cancel: make(chan struct{}),
cancelled: make(chan struct{}),
}
go func() {
defer close(task.cancelled)
ticker := time.NewTicker(interval)
defer func() {
ticker.Stop()
}()
for {
select {
case <-ticker.C:
function()
case <-task.cancel:
return
}
if !task.Recurring {
break
}
}
}()
return task
}
func (task *ScheduledTask) Cancel() {
close(task.cancel)
<-task.cancelled
}
func (task *ScheduledTask) String() string {
return fmt.Sprintf(
"%s\nInterval: %s\nRecurring: %t\n",
task.Name,
task.Interval.String(),
task.Recurring,
)
}

View File

@ -0,0 +1,227 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"regexp"
)
const (
SCHEME_DISPLAY_NAME_MAX_LENGTH = 128
SCHEME_NAME_MAX_LENGTH = 64
SCHEME_DESCRIPTION_MAX_LENGTH = 1024
SCHEME_SCOPE_TEAM = "team"
SCHEME_SCOPE_CHANNEL = "channel"
)
type Scheme struct {
Id string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
Scope string `json:"scope"`
DefaultTeamAdminRole string `json:"default_team_admin_role"`
DefaultTeamUserRole string `json:"default_team_user_role"`
DefaultChannelAdminRole string `json:"default_channel_admin_role"`
DefaultChannelUserRole string `json:"default_channel_user_role"`
DefaultTeamGuestRole string `json:"default_team_guest_role"`
DefaultChannelGuestRole string `json:"default_channel_guest_role"`
}
type SchemePatch struct {
Name *string `json:"name"`
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
}
type SchemeIDPatch struct {
SchemeID *string `json:"scheme_id"`
}
// SchemeConveyor is used for importing and exporting a Scheme and its associated Roles.
type SchemeConveyor struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Scope string `json:"scope"`
TeamAdmin string `json:"default_team_admin_role"`
TeamUser string `json:"default_team_user_role"`
TeamGuest string `json:"default_team_guest_role"`
ChannelAdmin string `json:"default_channel_admin_role"`
ChannelUser string `json:"default_channel_user_role"`
ChannelGuest string `json:"default_channel_guest_role"`
Roles []*Role `json:"roles"`
}
func (sc *SchemeConveyor) Scheme() *Scheme {
return &Scheme{
DisplayName: sc.DisplayName,
Name: sc.Name,
Description: sc.Description,
Scope: sc.Scope,
DefaultTeamAdminRole: sc.TeamAdmin,
DefaultTeamUserRole: sc.TeamUser,
DefaultTeamGuestRole: sc.TeamGuest,
DefaultChannelAdminRole: sc.ChannelAdmin,
DefaultChannelUserRole: sc.ChannelUser,
DefaultChannelGuestRole: sc.ChannelGuest,
}
}
type SchemeRoles struct {
SchemeAdmin bool `json:"scheme_admin"`
SchemeUser bool `json:"scheme_user"`
SchemeGuest bool `json:"scheme_guest"`
}
func (scheme *Scheme) ToJson() string {
b, _ := json.Marshal(scheme)
return string(b)
}
func SchemeFromJson(data io.Reader) *Scheme {
var scheme *Scheme
json.NewDecoder(data).Decode(&scheme)
return scheme
}
func SchemesToJson(schemes []*Scheme) string {
b, _ := json.Marshal(schemes)
return string(b)
}
func SchemesFromJson(data io.Reader) []*Scheme {
var schemes []*Scheme
if err := json.NewDecoder(data).Decode(&schemes); err == nil {
return schemes
} else {
return nil
}
}
func (scheme *Scheme) IsValid() bool {
if !IsValidId(scheme.Id) {
return false
}
return scheme.IsValidForCreate()
}
func (scheme *Scheme) IsValidForCreate() bool {
if len(scheme.DisplayName) == 0 || len(scheme.DisplayName) > SCHEME_DISPLAY_NAME_MAX_LENGTH {
return false
}
if !IsValidSchemeName(scheme.Name) {
return false
}
if len(scheme.Description) > SCHEME_DESCRIPTION_MAX_LENGTH {
return false
}
switch scheme.Scope {
case SCHEME_SCOPE_TEAM, SCHEME_SCOPE_CHANNEL:
default:
return false
}
if !IsValidRoleName(scheme.DefaultChannelAdminRole) {
return false
}
if !IsValidRoleName(scheme.DefaultChannelUserRole) {
return false
}
if !IsValidRoleName(scheme.DefaultChannelGuestRole) {
return false
}
if scheme.Scope == SCHEME_SCOPE_TEAM {
if !IsValidRoleName(scheme.DefaultTeamAdminRole) {
return false
}
if !IsValidRoleName(scheme.DefaultTeamUserRole) {
return false
}
if !IsValidRoleName(scheme.DefaultTeamGuestRole) {
return false
}
}
if scheme.Scope == SCHEME_SCOPE_CHANNEL {
if len(scheme.DefaultTeamAdminRole) != 0 {
return false
}
if len(scheme.DefaultTeamUserRole) != 0 {
return false
}
if len(scheme.DefaultTeamGuestRole) != 0 {
return false
}
}
return true
}
func (scheme *Scheme) Patch(patch *SchemePatch) {
if patch.DisplayName != nil {
scheme.DisplayName = *patch.DisplayName
}
if patch.Name != nil {
scheme.Name = *patch.Name
}
if patch.Description != nil {
scheme.Description = *patch.Description
}
}
func (patch *SchemePatch) ToJson() string {
b, _ := json.Marshal(patch)
return string(b)
}
func SchemePatchFromJson(data io.Reader) *SchemePatch {
var patch *SchemePatch
json.NewDecoder(data).Decode(&patch)
return patch
}
func SchemeIDFromJson(data io.Reader) *string {
var p *SchemeIDPatch
json.NewDecoder(data).Decode(&p)
return p.SchemeID
}
func (p *SchemeIDPatch) ToJson() string {
b, _ := json.Marshal(p)
return string(b)
}
func IsValidSchemeName(name string) bool {
re := regexp.MustCompile(fmt.Sprintf("^[a-z0-9_]{2,%d}$", SCHEME_NAME_MAX_LENGTH))
return re.MatchString(name)
}
func (schemeRoles *SchemeRoles) ToJson() string {
b, _ := json.Marshal(schemeRoles)
return string(b)
}
func SchemeRolesFromJson(data io.Reader) *SchemeRoles {
var schemeRoles *SchemeRoles
json.NewDecoder(data).Decode(&schemeRoles)
return schemeRoles
}

View File

@ -0,0 +1,369 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"regexp"
"strings"
"time"
)
var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`)
var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`)
type SearchParams struct {
Terms string
ExcludedTerms string
IsHashtag bool
InChannels []string
ExcludedChannels []string
FromUsers []string
ExcludedUsers []string
AfterDate string
ExcludedAfterDate string
BeforeDate string
ExcludedBeforeDate string
OnDate string
ExcludedDate string
OrTerms bool
IncludeDeletedChannels bool
TimeZoneOffset int
// True if this search doesn't originate from a "current user".
SearchWithoutUserId bool
}
// Returns the epoch timestamp of the start of the day specified by SearchParams.AfterDate
func (p *SearchParams) GetAfterDateMillis() int64 {
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.AfterDate))
if err != nil {
date = time.Now()
}
// travel forward 1 day
oneDay := time.Hour * 24
afterDate := date.Add(oneDay)
return GetStartOfDayMillis(afterDate, p.TimeZoneOffset)
}
// Returns the epoch timestamp of the start of the day specified by SearchParams.ExcludedAfterDate
func (p *SearchParams) GetExcludedAfterDateMillis() int64 {
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedAfterDate))
if err != nil {
date = time.Now()
}
// travel forward 1 day
oneDay := time.Hour * 24
afterDate := date.Add(oneDay)
return GetStartOfDayMillis(afterDate, p.TimeZoneOffset)
}
// Returns the epoch timestamp of the end of the day specified by SearchParams.BeforeDate
func (p *SearchParams) GetBeforeDateMillis() int64 {
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.BeforeDate))
if err != nil {
return 0
}
// travel back 1 day
oneDay := time.Hour * -24
beforeDate := date.Add(oneDay)
return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset)
}
// Returns the epoch timestamp of the end of the day specified by SearchParams.ExcludedBeforeDate
func (p *SearchParams) GetExcludedBeforeDateMillis() int64 {
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedBeforeDate))
if err != nil {
return 0
}
// travel back 1 day
oneDay := time.Hour * -24
beforeDate := date.Add(oneDay)
return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset)
}
// Returns the epoch timestamps of the start and end of the day specified by SearchParams.OnDate
func (p *SearchParams) GetOnDateMillis() (int64, int64) {
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.OnDate))
if err != nil {
return 0, 0
}
return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
}
// Returns the epoch timestamps of the start and end of the day specified by SearchParams.ExcludedDate
func (p *SearchParams) GetExcludedDateMillis() (int64, int64) {
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedDate))
if err != nil {
return 0, 0
}
return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
}
var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on"}
type flag struct {
name string
value string
exclude bool
}
type searchWord struct {
value string
exclude bool
}
func splitWords(text string) []string {
words := []string{}
foundQuote := false
location := 0
for i, char := range text {
if char == '"' {
if foundQuote {
// Grab the quoted section
word := text[location : i+1]
words = append(words, word)
foundQuote = false
location = i + 1
} else {
nextStart := i
if i > 0 && text[i-1] == '-' {
nextStart = i - 1
}
words = append(words, strings.Fields(text[location:nextStart])...)
foundQuote = true
location = nextStart
}
}
}
words = append(words, strings.Fields(text[location:])...)
return words
}
func parseSearchFlags(input []string) ([]searchWord, []flag) {
words := []searchWord{}
flags := []flag{}
skipNextWord := false
for i, word := range input {
if skipNextWord {
skipNextWord = false
continue
}
isFlag := false
if colon := strings.Index(word, ":"); colon != -1 {
var flagName string
var exclude bool
if strings.HasPrefix(word, "-") {
flagName = word[1:colon]
exclude = true
} else {
flagName = word[:colon]
exclude = false
}
value := word[colon+1:]
for _, searchFlag := range searchFlags {
// check for case insensitive equality
if strings.EqualFold(flagName, searchFlag) {
if value != "" {
flags = append(flags, flag{
searchFlag,
value,
exclude,
})
isFlag = true
} else if i < len(input)-1 {
flags = append(flags, flag{
searchFlag,
input[i+1],
exclude,
})
skipNextWord = true
isFlag = true
}
if isFlag {
break
}
}
}
}
if !isFlag {
exclude := false
if strings.HasPrefix(word, "-") {
exclude = true
}
// trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards)
word = searchTermPuncStart.ReplaceAllString(word, "")
word = searchTermPuncEnd.ReplaceAllString(word, "")
// and remove extra pound #s
word = hashtagStart.ReplaceAllString(word, "#")
if len(word) != 0 {
words = append(words, searchWord{
word,
exclude,
})
}
}
}
return words, flags
}
func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
words, flags := parseSearchFlags(splitWords(text))
hashtagTermList := []string{}
excludedHashtagTermList := []string{}
plainTermList := []string{}
excludedPlainTermList := []string{}
for _, word := range words {
if validHashtag.MatchString(word.value) {
if word.exclude {
excludedHashtagTermList = append(excludedHashtagTermList, word.value)
} else {
hashtagTermList = append(hashtagTermList, word.value)
}
} else {
if word.exclude {
excludedPlainTermList = append(excludedPlainTermList, word.value)
} else {
plainTermList = append(plainTermList, word.value)
}
}
}
hashtagTerms := strings.Join(hashtagTermList, " ")
excludedHashtagTerms := strings.Join(excludedHashtagTermList, " ")
plainTerms := strings.Join(plainTermList, " ")
excludedPlainTerms := strings.Join(excludedPlainTermList, " ")
inChannels := []string{}
excludedChannels := []string{}
fromUsers := []string{}
excludedUsers := []string{}
afterDate := ""
excludedAfterDate := ""
beforeDate := ""
excludedBeforeDate := ""
onDate := ""
excludedDate := ""
for _, flag := range flags {
if flag.name == "in" || flag.name == "channel" {
if flag.exclude {
excludedChannels = append(excludedChannels, flag.value)
} else {
inChannels = append(inChannels, flag.value)
}
} else if flag.name == "from" {
if flag.exclude {
excludedUsers = append(excludedUsers, flag.value)
} else {
fromUsers = append(fromUsers, flag.value)
}
} else if flag.name == "after" {
if flag.exclude {
excludedAfterDate = flag.value
} else {
afterDate = flag.value
}
} else if flag.name == "before" {
if flag.exclude {
excludedBeforeDate = flag.value
} else {
beforeDate = flag.value
}
} else if flag.name == "on" {
if flag.exclude {
excludedDate = flag.value
} else {
onDate = flag.value
}
}
}
paramsList := []*SearchParams{}
if len(plainTerms) > 0 || len(excludedPlainTerms) > 0 {
paramsList = append(paramsList, &SearchParams{
Terms: plainTerms,
ExcludedTerms: excludedPlainTerms,
IsHashtag: false,
InChannels: inChannels,
ExcludedChannels: excludedChannels,
FromUsers: fromUsers,
ExcludedUsers: excludedUsers,
AfterDate: afterDate,
ExcludedAfterDate: excludedAfterDate,
BeforeDate: beforeDate,
ExcludedBeforeDate: excludedBeforeDate,
OnDate: onDate,
ExcludedDate: excludedDate,
TimeZoneOffset: timeZoneOffset,
})
}
if len(hashtagTerms) > 0 || len(excludedHashtagTerms) > 0 {
paramsList = append(paramsList, &SearchParams{
Terms: hashtagTerms,
ExcludedTerms: excludedHashtagTerms,
IsHashtag: true,
InChannels: inChannels,
ExcludedChannels: excludedChannels,
FromUsers: fromUsers,
ExcludedUsers: excludedUsers,
AfterDate: afterDate,
ExcludedAfterDate: excludedAfterDate,
BeforeDate: beforeDate,
ExcludedBeforeDate: excludedBeforeDate,
OnDate: onDate,
ExcludedDate: excludedDate,
TimeZoneOffset: timeZoneOffset,
})
}
// 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 &&
(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) {
paramsList = append(paramsList, &SearchParams{
Terms: "",
ExcludedTerms: "",
IsHashtag: false,
InChannels: inChannels,
ExcludedChannels: excludedChannels,
FromUsers: fromUsers,
ExcludedUsers: excludedUsers,
AfterDate: afterDate,
ExcludedAfterDate: excludedAfterDate,
BeforeDate: beforeDate,
ExcludedBeforeDate: excludedBeforeDate,
OnDate: onDate,
ExcludedDate: excludedDate,
TimeZoneOffset: timeZoneOffset,
})
}
return paramsList
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type SecurityBulletin struct {
Id string `json:"id"`
AppliesToVersion string `json:"applies_to_version"`
}
type SecurityBulletins []SecurityBulletin
func (me *SecurityBulletin) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func SecurityBulletinFromJson(data io.Reader) *SecurityBulletin {
var o *SecurityBulletin
json.NewDecoder(data).Decode(&o)
return o
}
func (me SecurityBulletins) ToJson() string {
if b, err := json.Marshal(me); err != nil {
return "[]"
} else {
return string(b)
}
}
func SecurityBulletinsFromJson(data io.Reader) SecurityBulletins {
var o SecurityBulletins
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,208 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"strconv"
"strings"
"github.com/mattermost/mattermost-server/v5/mlog"
)
const (
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
SESSION_COOKIE_USER = "MMUSERID"
SESSION_COOKIE_CSRF = "MMCSRF"
SESSION_CACHE_SIZE = 35000
SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
SESSION_PROP_TYPE = "type"
SESSION_PROP_USER_ACCESS_TOKEN_ID = "user_access_token_id"
SESSION_PROP_IS_BOT = "is_bot"
SESSION_PROP_IS_BOT_VALUE = "true"
SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken"
SESSION_PROP_IS_GUEST = "is_guest"
SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years
)
type Session struct {
Id string `json:"id"`
Token string `json:"token"`
CreateAt int64 `json:"create_at"`
ExpiresAt int64 `json:"expires_at"`
LastActivityAt int64 `json:"last_activity_at"`
UserId string `json:"user_id"`
DeviceId string `json:"device_id"`
Roles string `json:"roles"`
IsOAuth bool `json:"is_oauth"`
Props StringMap `json:"props"`
TeamMembers []*TeamMember `json:"team_members" db:"-"`
Local bool `json:"local" db:"-"`
}
// 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 (me *Session) DeepCopy() *Session {
copySession := *me
if me.Props != nil {
copySession.Props = CopyStringMap(me.Props)
}
if me.TeamMembers != nil {
copySession.TeamMembers = make([]*TeamMember, len(me.TeamMembers))
for index, tm := range me.TeamMembers {
copySession.TeamMembers[index] = new(TeamMember)
*copySession.TeamMembers[index] = *tm
}
}
return &copySession
}
func (me *Session) ToJson() string {
b, _ := json.Marshal(me)
return string(b)
}
func SessionFromJson(data io.Reader) *Session {
var me *Session
json.NewDecoder(data).Decode(&me)
return me
}
func (me *Session) PreSave() {
if me.Id == "" {
me.Id = NewId()
}
if me.Token == "" {
me.Token = NewId()
}
me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt
if me.Props == nil {
me.Props = make(map[string]string)
}
}
func (me *Session) Sanitize() {
me.Token = ""
}
func (me *Session) IsExpired() bool {
if me.ExpiresAt <= 0 {
return false
}
if GetMillis() > me.ExpiresAt {
return true
}
return false
}
func (me *Session) SetExpireInDays(days int) {
if me.CreateAt == 0 {
me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
} else {
me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
}
}
func (me *Session) AddProp(key string, value string) {
if me.Props == nil {
me.Props = make(map[string]string)
}
me.Props[key] = value
}
func (me *Session) GetTeamByTeamId(teamId string) *TeamMember {
for _, team := range me.TeamMembers {
if team.TeamId == teamId {
return team
}
}
return nil
}
func (me *Session) IsMobileApp() bool {
return len(me.DeviceId) > 0 || me.IsMobile()
}
func (me *Session) IsMobile() bool {
val, ok := me.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))
return false
}
return isMobile
}
func (me *Session) IsSaml() bool {
val, ok := me.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))
return false
}
return isSaml
}
func (me *Session) IsSSOLogin() bool {
return me.IsOAuth || me.IsSaml()
}
func (me *Session) GetUserRoles() []string {
return strings.Fields(me.Roles)
}
func (me *Session) GenerateCSRF() string {
token := NewId()
me.AddProp("csrf", token)
return token
}
func (me *Session) GetCSRF() string {
if me.Props == nil {
return ""
}
return me.Props["csrf"]
}
func SessionsToJson(o []*Session) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func SessionsFromJson(data io.Reader) []*Session {
var o []*Session
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,193 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"fmt"
"regexp"
)
var linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
type SlackAttachment struct {
Id int64 `json:"id"`
Fallback string `json:"fallback"`
Color string `json:"color"`
Pretext string `json:"pretext"`
AuthorName string `json:"author_name"`
AuthorLink string `json:"author_link"`
AuthorIcon string `json:"author_icon"`
Title string `json:"title"`
TitleLink string `json:"title_link"`
Text string `json:"text"`
Fields []*SlackAttachmentField `json:"fields"`
ImageURL string `json:"image_url"`
ThumbURL string `json:"thumb_url"`
Footer string `json:"footer"`
FooterIcon string `json:"footer_icon"`
Timestamp interface{} `json:"ts"` // This is either a string or an int64
Actions []*PostAction `json:"actions,omitempty"`
}
func (s *SlackAttachment) Equals(input *SlackAttachment) bool {
// Direct comparison of simple types
if s.Id != input.Id {
return false
}
if s.Fallback != input.Fallback {
return false
}
if s.Color != input.Color {
return false
}
if s.Pretext != input.Pretext {
return false
}
if s.AuthorName != input.AuthorName {
return false
}
if s.AuthorLink != input.AuthorLink {
return false
}
if s.AuthorIcon != input.AuthorIcon {
return false
}
if s.Title != input.Title {
return false
}
if s.TitleLink != input.TitleLink {
return false
}
if s.Text != input.Text {
return false
}
if s.ImageURL != input.ImageURL {
return false
}
if s.ThumbURL != input.ThumbURL {
return false
}
if s.Footer != input.Footer {
return false
}
if s.FooterIcon != input.FooterIcon {
return false
}
// Compare length & slice values of fields
if len(s.Fields) != len(input.Fields) {
return false
}
for j := range s.Fields {
if !s.Fields[j].Equals(input.Fields[j]) {
return false
}
}
// Compare length & slice values of actions
if len(s.Actions) != len(input.Actions) {
return false
}
for j := range s.Actions {
if !s.Actions[j].Equals(input.Actions[j]) {
return false
}
}
return s.Timestamp == input.Timestamp
}
type SlackAttachmentField struct {
Title string `json:"title"`
Value interface{} `json:"value"`
Short SlackCompatibleBool `json:"short"`
}
func (s *SlackAttachmentField) Equals(input *SlackAttachmentField) bool {
if s.Title != input.Title {
return false
}
if s.Value != input.Value {
return false
}
if s.Short != input.Short {
return false
}
return true
}
func StringifySlackFieldValue(a []*SlackAttachment) []*SlackAttachment {
var nonNilAttachments []*SlackAttachment
for _, attachment := range a {
if attachment == nil {
continue
}
nonNilAttachments = append(nonNilAttachments, attachment)
var nonNilFields []*SlackAttachmentField
for _, field := range attachment.Fields {
if field == nil {
continue
}
nonNilFields = append(nonNilFields, field)
if field.Value != nil {
// Ensure the value is set to a string if it is set
field.Value = fmt.Sprintf("%v", field.Value)
}
}
attachment.Fields = nonNilFields
}
return nonNilAttachments
}
// This method only parses and processes the attachments,
// all else should be set in the post which is passed
func ParseSlackAttachment(post *Post, attachments []*SlackAttachment) {
if post.Type == "" {
post.Type = POST_SLACK_ATTACHMENT
}
postAttachments := []*SlackAttachment{}
for _, attachment := range attachments {
if attachment == nil {
continue
}
attachment.Text = ParseSlackLinksToMarkdown(attachment.Text)
attachment.Pretext = ParseSlackLinksToMarkdown(attachment.Pretext)
for _, field := range attachment.Fields {
if value, ok := field.Value.(string); ok {
field.Value = ParseSlackLinksToMarkdown(value)
}
}
postAttachments = append(postAttachments, attachment)
}
post.AddProp("attachments", postAttachments)
}
func ParseSlackLinksToMarkdown(text string) string {
return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
}

View File

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"fmt"
"strings"
)
// SlackCompatibleBool is an alias for bool that implements json.Unmarshaler
type SlackCompatibleBool bool
// UnmarshalJSON implements json.Unmarshaler
//
// Slack allows bool values to be represented as strings ("true"/"false") or
// literals (true/false). To maintain compatibility, we define an Unmarshaler
// that supports both.
func (b *SlackCompatibleBool) UnmarshalJSON(data []byte) error {
value := strings.ToLower(string(data))
if value == "true" || value == `"true"` {
*b = true
} else if value == "false" || value == `"false"` {
*b = false
} else {
return fmt.Errorf("unmarshal: unable to convert %s to bool", data)
}
return nil
}

View File

@ -0,0 +1,75 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
STATUS_OUT_OF_OFFICE = "ooo"
STATUS_OFFLINE = "offline"
STATUS_AWAY = "away"
STATUS_DND = "dnd"
STATUS_ONLINE = "online"
STATUS_CACHE_SIZE = SESSION_CACHE_SIZE
STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes
)
type Status struct {
UserId string `json:"user_id"`
Status string `json:"status"`
Manual bool `json:"manual"`
LastActivityAt int64 `json:"last_activity_at"`
ActiveChannel string `json:"active_channel,omitempty" db:"-"`
}
func (o *Status) ToJson() string {
oCopy := *o
oCopy.ActiveChannel = ""
b, _ := json.Marshal(oCopy)
return string(b)
}
func (o *Status) ToClusterJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func StatusFromJson(data io.Reader) *Status {
var o *Status
json.NewDecoder(data).Decode(&o)
return o
}
func StatusListToJson(u []*Status) string {
uCopy := make([]Status, len(u))
for i, s := range u {
sCopy := *s
sCopy.ActiveChannel = ""
uCopy[i] = sCopy
}
b, _ := json.Marshal(uCopy)
return string(b)
}
func StatusListFromJson(data io.Reader) []*Status {
var statuses []*Status
json.NewDecoder(data).Decode(&statuses)
return statuses
}
func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} {
interfaceMap := map[string]interface{}{}
for _, s := range statusMap {
// Omitted statues mean offline
if s.Status != STATUS_OFFLINE {
interfaceMap[s.UserId] = s.Status
}
}
return interfaceMap
}

View File

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type SuggestCommand struct {
Suggestion string `json:"suggestion"`
Description string `json:"description"`
}
func (o *SuggestCommand) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func SuggestCommandFromJson(data io.Reader) *SuggestCommand {
var o *SuggestCommand
json.NewDecoder(data).Decode(&o)
return o
}

View File

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

View File

@ -0,0 +1,71 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"math/big"
)
const (
SYSTEM_DIAGNOSTIC_ID = "DiagnosticId"
SYSTEM_RAN_UNIT_TESTS = "RanUnitTests"
SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime"
SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId"
SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime"
SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey"
SYSTEM_POST_ACTION_COOKIE_SECRET = "PostActionCookieSecret"
SYSTEM_INSTALLATION_DATE_KEY = "InstallationDate"
SYSTEM_FIRST_SERVER_RUN_TIMESTAMP_KEY = "FirstServerRunTimestamp"
)
type System struct {
Name string `json:"name"`
Value string `json:"value"`
}
func (o *System) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func SystemFromJson(data io.Reader) *System {
var o *System
json.NewDecoder(data).Decode(&o)
return o
}
type SystemPostActionCookieSecret struct {
Secret []byte `json:"key,omitempty"`
}
type SystemAsymmetricSigningKey struct {
ECDSAKey *SystemECDSAKey `json:"ecdsa_key,omitempty"`
}
type SystemECDSAKey struct {
Curve string `json:"curve"`
X *big.Int `json:"x"`
Y *big.Int `json:"y"`
D *big.Int `json:"d,omitempty"`
}
// ServerBusyState provides serialization for app.Busy.
type ServerBusyState struct {
Busy bool `json:"busy"`
Expires int64 `json:"expires"`
Expires_ts string `json:"expires_ts,omitempty"`
}
func (sbs *ServerBusyState) ToJson() string {
b, _ := json.Marshal(sbs)
return string(b)
}
func ServerBusyStateFromJson(r io.Reader) *ServerBusyState {
var sbs *ServerBusyState
json.NewDecoder(r).Decode(&sbs)
return sbs
}

View File

@ -0,0 +1,330 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"unicode/utf8"
)
const (
TEAM_OPEN = "O"
TEAM_INVITE = "I"
TEAM_ALLOWED_DOMAINS_MAX_LENGTH = 500
TEAM_COMPANY_NAME_MAX_LENGTH = 64
TEAM_DESCRIPTION_MAX_LENGTH = 255
TEAM_DISPLAY_NAME_MAX_RUNES = 64
TEAM_EMAIL_MAX_LENGTH = 128
TEAM_NAME_MAX_LENGTH = 64
TEAM_NAME_MIN_LENGTH = 2
)
type Team struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Description string `json:"description"`
Email string `json:"email"`
Type string `json:"type"`
CompanyName string `json:"company_name"`
AllowedDomains string `json:"allowed_domains"`
InviteId string `json:"invite_id"`
AllowOpenInvite bool `json:"allow_open_invite"`
LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"`
SchemeId *string `json:"scheme_id"`
GroupConstrained *bool `json:"group_constrained"`
}
type TeamPatch struct {
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
CompanyName *string `json:"company_name"`
AllowedDomains *string `json:"allowed_domains"`
AllowOpenInvite *bool `json:"allow_open_invite"`
GroupConstrained *bool `json:"group_constrained"`
}
type TeamForExport struct {
Team
SchemeName *string
}
type Invites struct {
Invites []map[string]string `json:"invites"`
}
type TeamsWithCount struct {
Teams []*Team `json:"teams"`
TotalCount int64 `json:"total_count"`
}
func InvitesFromJson(data io.Reader) *Invites {
var o *Invites
json.NewDecoder(data).Decode(&o)
return o
}
func (o *Invites) ToEmailList() []string {
emailList := make([]string, len(o.Invites))
for _, invite := range o.Invites {
emailList = append(emailList, invite["email"])
}
return emailList
}
func (o *Invites) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func (o *Team) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func TeamFromJson(data io.Reader) *Team {
var o *Team
json.NewDecoder(data).Decode(&o)
return o
}
func TeamMapToJson(u map[string]*Team) string {
b, _ := json.Marshal(u)
return string(b)
}
func TeamMapFromJson(data io.Reader) map[string]*Team {
var teams map[string]*Team
json.NewDecoder(data).Decode(&teams)
return teams
}
func TeamListToJson(t []*Team) string {
b, _ := json.Marshal(t)
return string(b)
}
func TeamsWithCountToJson(tlc *TeamsWithCount) []byte {
b, _ := json.Marshal(tlc)
return b
}
func TeamsWithCountFromJson(data io.Reader) *TeamsWithCount {
var twc *TeamsWithCount
json.NewDecoder(data).Decode(&twc)
return twc
}
func TeamListFromJson(data io.Reader) []*Team {
var teams []*Team
json.NewDecoder(data).Decode(&teams)
return teams
}
func (o *Team) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
func (o *Team) IsValid() *AppError {
if !IsValidId(o.Id) {
return NewAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.Email) > TEAM_EMAIL_MAX_LENGTH {
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) {
return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > TEAM_DISPLAY_NAME_MAX_RUNES {
return NewAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.Name) > TEAM_NAME_MAX_LENGTH {
return NewAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.Description) > TEAM_DESCRIPTION_MAX_LENGTH {
return NewAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.InviteId) == 0 {
return NewAppError("Team.IsValid", "model.team.is_valid.invite_id.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if IsReservedTeamName(o.Name) {
return NewAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidTeamName(o.Name) {
return NewAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) {
return NewAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.CompanyName) > TEAM_COMPANY_NAME_MAX_LENGTH {
return NewAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.AllowedDomains) > TEAM_ALLOWED_DOMAINS_MAX_LENGTH {
return NewAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
return nil
}
func (o *Team) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
o.Name = SanitizeUnicode(o.Name)
o.DisplayName = SanitizeUnicode(o.DisplayName)
o.Description = SanitizeUnicode(o.Description)
o.CompanyName = SanitizeUnicode(o.CompanyName)
if len(o.InviteId) == 0 {
o.InviteId = NewId()
}
}
func (o *Team) PreUpdate() {
o.UpdateAt = GetMillis()
o.Name = SanitizeUnicode(o.Name)
o.DisplayName = SanitizeUnicode(o.DisplayName)
o.Description = SanitizeUnicode(o.Description)
o.CompanyName = SanitizeUnicode(o.CompanyName)
}
func IsReservedTeamName(s string) bool {
s = strings.ToLower(s)
for _, value := range reservedName {
if strings.Index(s, value) == 0 {
return true
}
}
return false
}
func IsValidTeamName(s string) bool {
if !IsValidAlphaNum(s) {
return false
}
if len(s) < TEAM_NAME_MIN_LENGTH {
return false
}
return true
}
var validTeamNameCharacter = regexp.MustCompile(`^[a-z0-9-]$`)
func CleanTeamName(s string) string {
s = strings.ToLower(strings.Replace(s, " ", "-", -1))
for _, value := range reservedName {
if strings.Index(s, value) == 0 {
s = strings.Replace(s, value, "", -1)
}
}
s = strings.TrimSpace(s)
for _, c := range s {
char := fmt.Sprintf("%c", c)
if !validTeamNameCharacter.MatchString(char) {
s = strings.Replace(s, char, "", -1)
}
}
s = strings.Trim(s, "-")
if !IsValidTeamName(s) {
s = NewId()
}
return s
}
func (o *Team) Sanitize() {
o.Email = ""
o.InviteId = ""
}
func (o *Team) Patch(patch *TeamPatch) {
if patch.DisplayName != nil {
o.DisplayName = *patch.DisplayName
}
if patch.Description != nil {
o.Description = *patch.Description
}
if patch.CompanyName != nil {
o.CompanyName = *patch.CompanyName
}
if patch.AllowedDomains != nil {
o.AllowedDomains = *patch.AllowedDomains
}
if patch.AllowOpenInvite != nil {
o.AllowOpenInvite = *patch.AllowOpenInvite
}
if patch.GroupConstrained != nil {
o.GroupConstrained = patch.GroupConstrained
}
}
func (o *Team) IsGroupConstrained() bool {
return o.GroupConstrained != nil && *o.GroupConstrained
}
func (t *TeamPatch) ToJson() string {
b, err := json.Marshal(t)
if err != nil {
return ""
}
return string(b)
}
func TeamPatchFromJson(data io.Reader) *TeamPatch {
decoder := json.NewDecoder(data)
var team TeamPatch
err := decoder.Decode(&team)
if err != nil {
return nil
}
return &team
}

View File

@ -0,0 +1,186 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
const (
USERNAME = "Username"
)
type TeamMember struct {
TeamId string `json:"team_id"`
UserId string `json:"user_id"`
Roles string `json:"roles"`
DeleteAt int64 `json:"delete_at"`
SchemeGuest bool `json:"scheme_guest"`
SchemeUser bool `json:"scheme_user"`
SchemeAdmin bool `json:"scheme_admin"`
ExplicitRoles string `json:"explicit_roles"`
}
type TeamUnread struct {
TeamId string `json:"team_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
}
type TeamMemberForExport struct {
TeamMember
TeamName string
}
type TeamMemberWithError struct {
UserId string `json:"user_id"`
Member *TeamMember `json:"member"`
Error *AppError `json:"error"`
}
type EmailInviteWithError struct {
Email string `json:"email"`
Error *AppError `json:"error"`
}
type TeamMembersGetOptions struct {
// Sort the team members. Accepts "Username", but defaults to "Id".
Sort string
// If true, exclude team members whose corresponding user is deleted.
ExcludeDeletedUsers bool
// Restrict to search in a list of teams and channels
ViewRestrictions *ViewUsersRestrictions
}
func (o *TeamMember) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func (o *TeamUnread) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func TeamMemberFromJson(data io.Reader) *TeamMember {
var o *TeamMember
json.NewDecoder(data).Decode(&o)
return o
}
func TeamUnreadFromJson(data io.Reader) *TeamUnread {
var o *TeamUnread
json.NewDecoder(data).Decode(&o)
return o
}
func EmailInviteWithErrorFromJson(data io.Reader) []*EmailInviteWithError {
var o []*EmailInviteWithError
json.NewDecoder(data).Decode(&o)
return o
}
func EmailInviteWithErrorToEmails(o []*EmailInviteWithError) []string {
var ret []string
for _, o := range o {
if o.Error == nil {
ret = append(ret, o.Email)
}
}
return ret
}
func EmailInviteWithErrorToJson(o []*EmailInviteWithError) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func EmailInviteWithErrorToString(o *EmailInviteWithError) string {
return fmt.Sprintf("%s:%s", o.Email, o.Error.Error())
}
func TeamMembersWithErrorToTeamMembers(o []*TeamMemberWithError) []*TeamMember {
var ret []*TeamMember
for _, o := range o {
if o.Error == nil {
ret = append(ret, o.Member)
}
}
return ret
}
func TeamMembersWithErrorToJson(o []*TeamMemberWithError) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func TeamMemberWithErrorToString(o *TeamMemberWithError) string {
return fmt.Sprintf("%s:%s", o.UserId, o.Error.Error())
}
func TeamMembersWithErrorFromJson(data io.Reader) []*TeamMemberWithError {
var o []*TeamMemberWithError
json.NewDecoder(data).Decode(&o)
return o
}
func TeamMembersToJson(o []*TeamMember) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func TeamMembersFromJson(data io.Reader) []*TeamMember {
var o []*TeamMember
json.NewDecoder(data).Decode(&o)
return o
}
func TeamsUnreadToJson(o []*TeamUnread) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func TeamsUnreadFromJson(data io.Reader) []*TeamUnread {
var o []*TeamUnread
json.NewDecoder(data).Decode(&o)
return o
}
func (o *TeamMember) IsValid() *AppError {
if !IsValidId(o.TeamId) {
return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(o.UserId) {
return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (o *TeamMember) PreUpdate() {
}
func (o *TeamMember) GetRoles() []string {
return strings.Fields(o.Roles)
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type TeamSearch struct {
Term string `json:"term"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
}
func (t *TeamSearch) IsPaginated() bool {
return t.Page != nil && t.PerPage != nil
}
// ToJson convert a TeamSearch to json string
func (t *TeamSearch) ToJson() string {
b, err := json.Marshal(t)
if err != nil {
return ""
}
return string(b)
}
// TeamSearchFromJson decodes the input and returns a TeamSearch
func TeamSearchFromJson(data io.Reader) *TeamSearch {
decoder := json.NewDecoder(data)
var cs TeamSearch
err := decoder.Decode(&cs)
if err == nil {
return &cs
}
return nil
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
)
type TeamStats struct {
TeamId string `json:"team_id"`
TotalMemberCount int64 `json:"total_member_count"`
ActiveMemberCount int64 `json:"active_member_count"`
}
func (o *TeamStats) ToJson() string {
b, _ := json.Marshal(o)
return string(b)
}
func TeamStatsFromJson(data io.Reader) *TeamStats {
var o *TeamStats
json.NewDecoder(data).Decode(&o)
return o
}

View File

@ -0,0 +1,69 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
"unicode/utf8"
)
const TERMS_OF_SERVICE_CACHE_SIZE = 1
type TermsOfService struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UserId string `json:"user_id"`
Text string `json:"text"`
}
func (t *TermsOfService) IsValid() *AppError {
if !IsValidId(t.Id) {
return InvalidTermsOfServiceError("id", "")
}
if t.CreateAt == 0 {
return InvalidTermsOfServiceError("create_at", t.Id)
}
if !IsValidId(t.UserId) {
return InvalidTermsOfServiceError("user_id", t.Id)
}
if utf8.RuneCountInString(t.Text) > POST_MESSAGE_MAX_RUNES_V2 {
return InvalidTermsOfServiceError("text", t.Id)
}
return nil
}
func (t *TermsOfService) ToJson() string {
b, _ := json.Marshal(t)
return string(b)
}
func TermsOfServiceFromJson(data io.Reader) *TermsOfService {
var termsOfService *TermsOfService
json.NewDecoder(data).Decode(&termsOfService)
return termsOfService
}
func InvalidTermsOfServiceError(fieldName string, termsOfServiceId string) *AppError {
id := fmt.Sprintf("model.terms_of_service.is_valid.%s.app_error", fieldName)
details := ""
if termsOfServiceId != "" {
details = "terms_of_service_id=" + termsOfServiceId
}
return NewAppError("TermsOfService.IsValid", id, map[string]interface{}{"MaxLength": POST_MESSAGE_MAX_RUNES_V2}, details, http.StatusBadRequest)
}
func (t *TermsOfService) PreSave() {
if t.Id == "" {
t.Id = NewId()
}
t.CreateAt = GetMillis()
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import "net/http"
const (
TOKEN_SIZE = 64
MAX_TOKEN_EXIPRY_TIME = 1000 * 60 * 60 * 48 // 48 hour
TOKEN_TYPE_OAUTH = "oauth"
)
type Token struct {
Token string
CreateAt int64
Type string
Extra string
}
func NewToken(tokentype, extra string) *Token {
return &Token{
Token: NewRandomString(TOKEN_SIZE),
CreateAt: GetMillis(),
Type: tokentype,
Extra: extra,
}
}
func (t *Token) IsValid() *AppError {
if len(t.Token) != TOKEN_SIZE {
return NewAppError("Token.IsValid", "model.token.is_valid.size", nil, "", http.StatusInternalServerError)
}
if t.CreateAt == 0 {
return NewAppError("Token.IsValid", "model.token.is_valid.expiry", nil, "", http.StatusInternalServerError)
}
return nil
}

View File

@ -0,0 +1,903 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"crypto/sha256"
"encoding/json"
"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"
)
const (
ME = "me"
USER_NOTIFY_ALL = "all"
USER_NOTIFY_HERE = "here"
USER_NOTIFY_MENTION = "mention"
USER_NOTIFY_NONE = "none"
DESKTOP_NOTIFY_PROP = "desktop"
DESKTOP_SOUND_NOTIFY_PROP = "desktop_sound"
MARK_UNREAD_NOTIFY_PROP = "mark_unread"
PUSH_NOTIFY_PROP = "push"
PUSH_STATUS_NOTIFY_PROP = "push_status"
EMAIL_NOTIFY_PROP = "email"
CHANNEL_MENTIONS_NOTIFY_PROP = "channel"
COMMENTS_NOTIFY_PROP = "comments"
MENTION_KEYS_NOTIFY_PROP = "mention_keys"
COMMENTS_NOTIFY_NEVER = "never"
COMMENTS_NOTIFY_ROOT = "root"
COMMENTS_NOTIFY_ANY = "any"
FIRST_NAME_NOTIFY_PROP = "first_name"
AUTO_RESPONDER_ACTIVE_NOTIFY_PROP = "auto_responder_active"
AUTO_RESPONDER_MESSAGE_NOTIFY_PROP = "auto_responder_message"
DEFAULT_LOCALE = "en"
USER_AUTH_SERVICE_EMAIL = "email"
USER_EMAIL_MAX_LENGTH = 128
USER_NICKNAME_MAX_RUNES = 64
USER_POSITION_MAX_RUNES = 128
USER_FIRST_NAME_MAX_RUNES = 64
USER_LAST_NAME_MAX_RUNES = 64
USER_AUTH_DATA_MAX_LENGTH = 128
USER_NAME_MAX_LENGTH = 64
USER_NAME_MIN_LENGTH = 1
USER_PASSWORD_MAX_LENGTH = 72
USER_LOCALE_MAX_LENGTH = 5
)
type User struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
DeleteAt int64 `json:"delete_at"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
AuthData *string `json:"auth_data,omitempty"`
AuthService string `json:"auth_service"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified,omitempty"`
Nickname string `json:"nickname"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Position string `json:"position"`
Roles string `json:"roles"`
AllowMarketing bool `json:"allow_marketing,omitempty"`
Props StringMap `json:"props,omitempty"`
NotifyProps StringMap `json:"notify_props,omitempty"`
LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
FailedAttempts int `json:"failed_attempts,omitempty"`
Locale string `json:"locale"`
Timezone StringMap `json:"timezone"`
MfaActive bool `json:"mfa_active,omitempty"`
MfaSecret string `json:"mfa_secret,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"`
}
type UserUpdate struct {
Old *User
New *User
}
type UserPatch struct {
Username *string `json:"username"`
Password *string `json:"password,omitempty"`
Nickname *string `json:"nickname"`
FirstName *string `json:"first_name"`
LastName *string `json:"last_name"`
Position *string `json:"position"`
Email *string `json:"email"`
Props StringMap `json:"props,omitempty"`
NotifyProps StringMap `json:"notify_props,omitempty"`
Locale *string `json:"locale"`
Timezone StringMap `json:"timezone"`
}
type UserAuth struct {
Password string `json:"password,omitempty"`
AuthData *string `json:"auth_data,omitempty"`
AuthService string `json:"auth_service,omitempty"`
}
type UserForIndexing struct {
Id string `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
TeamsIds []string `json:"team_id"`
ChannelsIds []string `json:"channel_id"`
}
type ViewUsersRestrictions struct {
Teams []string
Channels []string
}
func (r *ViewUsersRestrictions) Hash() string {
if r == nil {
return ""
}
ids := append(r.Teams, r.Channels...)
sort.Strings(ids)
hash := sha256.New()
hash.Write([]byte(strings.Join(ids, "")))
return fmt.Sprintf("%x", hash.Sum(nil))
}
type UserSlice []*User
func (u UserSlice) Usernames() []string {
usernames := []string{}
for _, user := range u {
usernames = append(usernames, user.Username)
}
sort.Strings(usernames)
return usernames
}
func (u UserSlice) IDs() []string {
ids := []string{}
for _, user := range u {
ids = append(ids, user.Id)
}
return ids
}
func (u UserSlice) FilterWithoutBots() UserSlice {
var matches []*User
for _, user := range u {
if !user.IsBot {
matches = append(matches, user)
}
}
return UserSlice(matches)
}
func (u UserSlice) FilterByActive(active bool) UserSlice {
var matches []*User
for _, user := range u {
if user.DeleteAt == 0 && active {
matches = append(matches, user)
} else if user.DeleteAt != 0 && !active {
matches = append(matches, user)
}
}
return UserSlice(matches)
}
func (u UserSlice) FilterByID(ids []string) UserSlice {
var matches []*User
for _, user := range u {
for _, id := range ids {
if id == user.Id {
matches = append(matches, user)
}
}
}
return UserSlice(matches)
}
func (u UserSlice) FilterWithoutID(ids []string) UserSlice {
var keep []*User
for _, user := range u {
present := false
for _, id := range ids {
if id == user.Id {
present = true
}
}
if !present {
keep = append(keep, user)
}
}
return UserSlice(keep)
}
func (u *User) DeepCopy() *User {
copyUser := *u
if u.AuthData != nil {
copyUser.AuthData = NewString(*u.AuthData)
}
if u.Props != nil {
copyUser.Props = CopyStringMap(u.Props)
}
if u.NotifyProps != nil {
copyUser.NotifyProps = CopyStringMap(u.NotifyProps)
}
if u.Timezone != nil {
copyUser.Timezone = CopyStringMap(u.Timezone)
}
return &copyUser
}
// IsValid validates the user and returns an error if it isn't configured
// correctly.
func (u *User) IsValid() *AppError {
if !IsValidId(u.Id) {
return InvalidUserError("id", "")
}
if u.CreateAt == 0 {
return InvalidUserError("create_at", u.Id)
}
if u.UpdateAt == 0 {
return InvalidUserError("update_at", u.Id)
}
if !IsValidUsername(u.Username) {
return InvalidUserError("username", u.Id)
}
if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 || !IsValidEmail(u.Email) {
return InvalidUserError("email", u.Id)
}
if utf8.RuneCountInString(u.Nickname) > USER_NICKNAME_MAX_RUNES {
return InvalidUserError("nickname", u.Id)
}
if utf8.RuneCountInString(u.Position) > USER_POSITION_MAX_RUNES {
return InvalidUserError("position", u.Id)
}
if utf8.RuneCountInString(u.FirstName) > USER_FIRST_NAME_MAX_RUNES {
return InvalidUserError("first_name", u.Id)
}
if utf8.RuneCountInString(u.LastName) > USER_LAST_NAME_MAX_RUNES {
return InvalidUserError("last_name", u.Id)
}
if u.AuthData != nil && len(*u.AuthData) > USER_AUTH_DATA_MAX_LENGTH {
return InvalidUserError("auth_data", u.Id)
}
if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 {
return InvalidUserError("auth_data_type", u.Id)
}
if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 {
return InvalidUserError("auth_data_pwd", u.Id)
}
if len(u.Password) > USER_PASSWORD_MAX_LENGTH {
return InvalidUserError("password_limit", u.Id)
}
if !IsValidLocale(u.Locale) {
return InvalidUserError("locale", u.Id)
}
return nil
}
func InvalidUserError(fieldName string, userId string) *AppError {
id := fmt.Sprintf("model.user.is_valid.%s.app_error", fieldName)
details := ""
if userId != "" {
details = "user_id=" + userId
}
return NewAppError("User.IsValid", id, nil, details, http.StatusBadRequest)
}
func NormalizeUsername(username string) string {
return strings.ToLower(username)
}
func NormalizeEmail(email string) string {
return strings.ToLower(email)
}
// PreSave will set the Id and Username if missing. It will also fill
// in the CreateAt, UpdateAt times. It will also hash the password. It should
// be run before saving the user to the db.
func (u *User) PreSave() {
if u.Id == "" {
u.Id = NewId()
}
if u.Username == "" {
u.Username = NewId()
}
if u.AuthData != nil && *u.AuthData == "" {
u.AuthData = nil
}
u.Username = SanitizeUnicode(u.Username)
u.FirstName = SanitizeUnicode(u.FirstName)
u.LastName = SanitizeUnicode(u.LastName)
u.Nickname = SanitizeUnicode(u.Nickname)
u.Username = NormalizeUsername(u.Username)
u.Email = NormalizeEmail(u.Email)
u.CreateAt = GetMillis()
u.UpdateAt = u.CreateAt
u.LastPasswordUpdate = u.CreateAt
u.MfaActive = false
if u.Locale == "" {
u.Locale = DEFAULT_LOCALE
}
if u.Props == nil {
u.Props = make(map[string]string)
}
if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
u.SetDefaultNotifications()
}
if u.Timezone == nil {
u.Timezone = timezones.DefaultUserTimezone()
}
if len(u.Password) > 0 {
u.Password = HashPassword(u.Password)
}
}
// PreUpdate should be run before updating the user in the db.
func (u *User) PreUpdate() {
u.Username = SanitizeUnicode(u.Username)
u.FirstName = SanitizeUnicode(u.FirstName)
u.LastName = SanitizeUnicode(u.LastName)
u.Nickname = SanitizeUnicode(u.Nickname)
u.BotDescription = SanitizeUnicode(u.BotDescription)
u.Username = NormalizeUsername(u.Username)
u.Email = NormalizeEmail(u.Email)
u.UpdateAt = GetMillis()
u.FirstName = SanitizeUnicode(u.FirstName)
u.LastName = SanitizeUnicode(u.LastName)
u.Nickname = SanitizeUnicode(u.Nickname)
u.BotDescription = SanitizeUnicode(u.BotDescription)
if u.AuthData != nil && *u.AuthData == "" {
u.AuthData = nil
}
if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
u.SetDefaultNotifications()
} else if _, ok := u.NotifyProps[MENTION_KEYS_NOTIFY_PROP]; ok {
// Remove any blank mention keys
splitKeys := strings.Split(u.NotifyProps[MENTION_KEYS_NOTIFY_PROP], ",")
goodKeys := []string{}
for _, key := range splitKeys {
if len(key) > 0 {
goodKeys = append(goodKeys, strings.ToLower(key))
}
}
u.NotifyProps[MENTION_KEYS_NOTIFY_PROP] = strings.Join(goodKeys, ",")
}
}
func (u *User) SetDefaultNotifications() {
u.NotifyProps = make(map[string]string)
u.NotifyProps[EMAIL_NOTIFY_PROP] = "true"
u.NotifyProps[PUSH_NOTIFY_PROP] = USER_NOTIFY_MENTION
u.NotifyProps[DESKTOP_NOTIFY_PROP] = USER_NOTIFY_MENTION
u.NotifyProps[DESKTOP_SOUND_NOTIFY_PROP] = "true"
u.NotifyProps[MENTION_KEYS_NOTIFY_PROP] = ""
u.NotifyProps[CHANNEL_MENTIONS_NOTIFY_PROP] = "true"
u.NotifyProps[PUSH_STATUS_NOTIFY_PROP] = STATUS_AWAY
u.NotifyProps[COMMENTS_NOTIFY_PROP] = COMMENTS_NOTIFY_NEVER
u.NotifyProps[FIRST_NAME_NOTIFY_PROP] = "false"
}
func (u *User) UpdateMentionKeysFromUsername(oldUsername string) {
nonUsernameKeys := []string{}
for _, key := range u.GetMentionKeys() {
if key != oldUsername && key != "@"+oldUsername {
nonUsernameKeys = append(nonUsernameKeys, key)
}
}
u.NotifyProps[MENTION_KEYS_NOTIFY_PROP] = ""
if len(nonUsernameKeys) > 0 {
u.NotifyProps[MENTION_KEYS_NOTIFY_PROP] += "," + strings.Join(nonUsernameKeys, ",")
}
}
func (u *User) GetMentionKeys() []string {
var keys []string
for _, key := range strings.Split(u.NotifyProps[MENTION_KEYS_NOTIFY_PROP], ",") {
trimmedKey := strings.TrimSpace(key)
if trimmedKey == "" {
continue
}
keys = append(keys, trimmedKey)
}
return keys
}
func (u *User) Patch(patch *UserPatch) {
if patch.Username != nil {
u.Username = *patch.Username
}
if patch.Nickname != nil {
u.Nickname = *patch.Nickname
}
if patch.FirstName != nil {
u.FirstName = *patch.FirstName
}
if patch.LastName != nil {
u.LastName = *patch.LastName
}
if patch.Position != nil {
u.Position = *patch.Position
}
if patch.Email != nil {
u.Email = *patch.Email
}
if patch.Props != nil {
u.Props = patch.Props
}
if patch.NotifyProps != nil {
u.NotifyProps = patch.NotifyProps
}
if patch.Locale != nil {
u.Locale = *patch.Locale
}
if patch.Timezone != nil {
u.Timezone = patch.Timezone
}
}
// ToJson convert a User to a json string
func (u *User) ToJson() string {
b, _ := json.Marshal(u)
return string(b)
}
func (u *UserPatch) ToJson() string {
b, _ := json.Marshal(u)
return string(b)
}
func (u *UserAuth) ToJson() string {
b, _ := json.Marshal(u)
return string(b)
}
// Generate a valid strong etag so the browser can cache the results
func (u *User) Etag(showFullName, showEmail bool) string {
return Etag(u.Id, u.UpdateAt, u.TermsOfServiceId, u.TermsOfServiceCreateAt, showFullName, showEmail, u.BotLastIconUpdate)
}
// Remove any private data from the user object
func (u *User) Sanitize(options map[string]bool) {
u.Password = ""
u.AuthData = NewString("")
u.MfaSecret = ""
if len(options) != 0 && !options["email"] {
u.Email = ""
}
if len(options) != 0 && !options["fullname"] {
u.FirstName = ""
u.LastName = ""
}
if len(options) != 0 && !options["passwordupdate"] {
u.LastPasswordUpdate = 0
}
if len(options) != 0 && !options["authservice"] {
u.AuthService = ""
}
}
// Remove any input data from the user object that is not user controlled
func (u *User) SanitizeInput(isAdmin bool) {
if !isAdmin {
u.AuthData = NewString("")
u.AuthService = ""
}
u.LastPasswordUpdate = 0
u.LastPictureUpdate = 0
u.FailedAttempts = 0
u.EmailVerified = false
u.MfaActive = false
u.MfaSecret = ""
}
func (u *User) ClearNonProfileFields() {
u.Password = ""
u.AuthData = NewString("")
u.MfaSecret = ""
u.EmailVerified = false
u.AllowMarketing = false
u.NotifyProps = StringMap{}
u.LastPasswordUpdate = 0
u.FailedAttempts = 0
}
func (u *User) SanitizeProfile(options map[string]bool) {
u.ClearNonProfileFields()
u.Sanitize(options)
}
func (u *User) MakeNonNil() {
if u.Props == nil {
u.Props = make(map[string]string)
}
if u.NotifyProps == nil {
u.NotifyProps = make(map[string]string)
}
}
func (u *User) AddNotifyProp(key string, value string) {
u.MakeNonNil()
u.NotifyProps[key] = value
}
func (u *User) GetFullName() string {
if len(u.FirstName) > 0 && len(u.LastName) > 0 {
return u.FirstName + " " + u.LastName
} else if len(u.FirstName) > 0 {
return u.FirstName
} else if len(u.LastName) > 0 {
return u.LastName
} else {
return ""
}
}
func (u *User) getDisplayName(baseName, nameFormat string) string {
displayName := baseName
if nameFormat == SHOW_NICKNAME_FULLNAME {
if len(u.Nickname) > 0 {
displayName = u.Nickname
} else if fullName := u.GetFullName(); len(fullName) > 0 {
displayName = fullName
}
} else if nameFormat == SHOW_FULLNAME {
if fullName := u.GetFullName(); len(fullName) > 0 {
displayName = fullName
}
}
return displayName
}
func (u *User) GetDisplayName(nameFormat string) string {
displayName := u.Username
return u.getDisplayName(displayName, nameFormat)
}
func (u *User) GetDisplayNameWithPrefix(nameFormat, prefix string) string {
displayName := prefix + u.Username
return u.getDisplayName(displayName, nameFormat)
}
func (u *User) GetRoles() []string {
return strings.Fields(u.Roles)
}
func (u *User) GetRawRoles() string {
return u.Roles
}
func IsValidUserRoles(userRoles string) bool {
roles := strings.Fields(userRoles)
for _, r := range roles {
if !IsValidRoleName(r) {
return false
}
}
// Exclude just the system_admin role explicitly to prevent mistakes
if len(roles) == 1 && roles[0] == "system_admin" {
return false
}
return true
}
// Make sure you acually want to use this function. In context.go there are functions to check permissions
// This function should not be used to check permissions.
func (u *User) IsGuest() bool {
return IsInRole(u.Roles, SYSTEM_GUEST_ROLE_ID)
}
func (u *User) IsSystemAdmin() bool {
return IsInRole(u.Roles, SYSTEM_ADMIN_ROLE_ID)
}
// Make sure you acually want to use this function. In context.go there are functions to check permissions
// This function should not be used to check permissions.
func (u *User) IsInRole(inRole string) bool {
return IsInRole(u.Roles, inRole)
}
// Make sure you acually want to use this function. In context.go there are functions to check permissions
// This function should not be used to check permissions.
func IsInRole(userRoles string, inRole string) bool {
roles := strings.Split(userRoles, " ")
for _, r := range roles {
if r == inRole {
return true
}
}
return false
}
func (u *User) IsSSOUser() bool {
return u.AuthService != "" && u.AuthService != USER_AUTH_SERVICE_EMAIL
}
func (u *User) IsOAuthUser() bool {
return u.AuthService == USER_AUTH_SERVICE_GITLAB
}
func (u *User) IsLDAPUser() bool {
return u.AuthService == USER_AUTH_SERVICE_LDAP
}
func (u *User) IsSAMLUser() bool {
return u.AuthService == USER_AUTH_SERVICE_SAML
}
func (u *User) GetPreferredTimezone() string {
return GetPreferredTimezone(u.Timezone)
}
// UserFromJson will decode the input and return a User
func UserFromJson(data io.Reader) *User {
var user *User
json.NewDecoder(data).Decode(&user)
return user
}
func UserPatchFromJson(data io.Reader) *UserPatch {
var user *UserPatch
json.NewDecoder(data).Decode(&user)
return user
}
func UserAuthFromJson(data io.Reader) *UserAuth {
var user *UserAuth
json.NewDecoder(data).Decode(&user)
return user
}
func UserMapToJson(u map[string]*User) string {
b, _ := json.Marshal(u)
return string(b)
}
func UserMapFromJson(data io.Reader) map[string]*User {
var users map[string]*User
json.NewDecoder(data).Decode(&users)
return users
}
func UserListToJson(u []*User) string {
b, _ := json.Marshal(u)
return string(b)
}
func UserListFromJson(data io.Reader) []*User {
var users []*User
json.NewDecoder(data).Decode(&users)
return users
}
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
func HashPassword(password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
panic(err)
}
return string(hash)
}
// ComparePassword compares the hash
func ComparePassword(hash string, password string) bool {
if len(password) == 0 || len(hash) == 0 {
return false
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
var restrictedUsernames = []string{
"all",
"channel",
"matterbot",
"system",
}
func IsValidUsername(s string) bool {
if len(s) < USER_NAME_MIN_LENGTH || len(s) > USER_NAME_MAX_LENGTH {
return false
}
if !validUsernameChars.MatchString(s) {
return false
}
for _, restrictedUsername := range restrictedUsernames {
if s == restrictedUsername {
return false
}
}
return true
}
func CleanUsername(s string) string {
s = NormalizeUsername(strings.Replace(s, " ", "-", -1))
for _, value := range reservedName {
if s == value {
s = strings.Replace(s, value, "", -1)
}
}
s = strings.TrimSpace(s)
for _, c := range s {
char := fmt.Sprintf("%c", c)
if !validUsernameChars.MatchString(char) {
s = strings.Replace(s, char, "-", -1)
}
}
s = strings.Trim(s, "-")
if !IsValidUsername(s) {
s = "a" + NewId()
}
return s
}
func IsValidUserNotifyLevel(notifyLevel string) bool {
return notifyLevel == CHANNEL_NOTIFY_ALL ||
notifyLevel == CHANNEL_NOTIFY_MENTION ||
notifyLevel == CHANNEL_NOTIFY_NONE
}
func IsValidPushStatusNotifyLevel(notifyLevel string) bool {
return notifyLevel == STATUS_ONLINE ||
notifyLevel == STATUS_AWAY ||
notifyLevel == STATUS_OFFLINE
}
func IsValidCommentsNotifyLevel(notifyLevel string) bool {
return notifyLevel == COMMENTS_NOTIFY_ANY ||
notifyLevel == COMMENTS_NOTIFY_ROOT ||
notifyLevel == COMMENTS_NOTIFY_NEVER
}
func IsValidEmailBatchingInterval(emailInterval string) bool {
return emailInterval == PREFERENCE_EMAIL_INTERVAL_IMMEDIATELY ||
emailInterval == PREFERENCE_EMAIL_INTERVAL_FIFTEEN ||
emailInterval == PREFERENCE_EMAIL_INTERVAL_HOUR
}
func IsValidLocale(locale string) bool {
if locale != "" {
if len(locale) > USER_LOCALE_MAX_LENGTH {
return false
} else if _, err := language.Parse(locale); err != nil {
return false
}
}
return true
}
type UserWithGroups struct {
User
GroupIDs *string `json:"-"`
Groups []*Group `json:"groups"`
SchemeGuest bool `json:"scheme_guest"`
SchemeUser bool `json:"scheme_user"`
SchemeAdmin bool `json:"scheme_admin"`
}
func (u *UserWithGroups) GetGroupIDs() []string {
if u.GroupIDs == nil {
return nil
}
trimmed := strings.TrimSpace(*u.GroupIDs)
if len(trimmed) == 0 {
return nil
}
return strings.Split(trimmed, ",")
}
type UsersWithGroupsAndCount struct {
Users []*UserWithGroups `json:"users"`
Count int64 `json:"total_count"`
}
func UsersWithGroupsAndCountFromJson(data io.Reader) *UsersWithGroupsAndCount {
uwg := &UsersWithGroupsAndCount{}
bodyBytes, _ := ioutil.ReadAll(data)
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

@ -0,0 +1,65 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/http"
)
type UserAccessToken struct {
Id string `json:"id"`
Token string `json:"token,omitempty"`
UserId string `json:"user_id"`
Description string `json:"description"`
IsActive bool `json:"is_active"`
}
func (t *UserAccessToken) IsValid() *AppError {
if !IsValidId(t.Id) {
return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(t.Token) != 26 {
return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.token.app_error", nil, "", http.StatusBadRequest)
}
if !IsValidId(t.UserId) {
return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(t.Description) > 255 {
return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.description.app_error", nil, "", http.StatusBadRequest)
}
return nil
}
func (t *UserAccessToken) PreSave() {
t.Id = NewId()
t.IsActive = true
}
func (t *UserAccessToken) ToJson() string {
b, _ := json.Marshal(t)
return string(b)
}
func UserAccessTokenFromJson(data io.Reader) *UserAccessToken {
var t *UserAccessToken
json.NewDecoder(data).Decode(&t)
return t
}
func UserAccessTokenListToJson(t []*UserAccessToken) string {
b, _ := json.Marshal(t)
return string(b)
}
func UserAccessTokenListFromJson(data io.Reader) []*UserAccessToken {
var t []*UserAccessToken
json.NewDecoder(data).Decode(&t)
return t
}

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