mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
* Posts * Add missing translation * Fix internal store marshaling * [MM-22051] Remove To/From JSON (Channels) (#18116) * Channels * Channel members * ChannelSearch * Channel categories, list, sidebar, stats, view * Fix conversions * [MM-22051] Remove To/From JSON (Users) (#18121) * User related structs * Fix return * Team related structures (#18127) * [MM-22051] Remove To/From JSON (Status, Bot, Reaction, Thread, FileInfo) (#18130) * Status * Bot * Reaction * Thread * FileInfo * Some fixes * Translations update from Weblate (#18143) * Translated using Weblate (German) Currently translated at 100.0% (2309 of 2309 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ * Translated using Weblate (Turkish) Currently translated at 100.0% (2309 of 2309 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2309 of 2309 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ * Translated using Weblate (Turkish) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ * Translated using Weblate (English (Australia)) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/en_AU/ * Translated using Weblate (Bulgarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/bg/ * Translated using Weblate (Japanese) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ja/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2301 of 2301 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/zh_Hans/ Co-authored-by: JtheBAB <srast@bioc.uzh.ch> Co-authored-by: Kaya Zeren <kayazeren@gmail.com> Co-authored-by: Tóth Csaba // Online ERP Hungary Kft <csaba.toth@online-erp.hu> Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au> Co-authored-by: Nikolai Zahariev <nikolaiz@yahoo.com> Co-authored-by: kaakaa <stooner.hoe@gmail.com> Co-authored-by: aeomin <lin@aeomin.net> Co-authored-by: Weblate (bot) <hosted@weblate.org> Co-authored-by: JtheBAB <srast@bioc.uzh.ch> Co-authored-by: Kaya Zeren <kayazeren@gmail.com> Co-authored-by: Tóth Csaba // Online ERP Hungary Kft <csaba.toth@online-erp.hu> Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au> Co-authored-by: Nikolai Zahariev <nikolaiz@yahoo.com> Co-authored-by: kaakaa <stooner.hoe@gmail.com> Co-authored-by: aeomin <lin@aeomin.net> * [MM-22051] Remove To/From JSON methods from model (#18138) * Scheme * Role * Session * Config * Status * Fix logic * Emoji * GuestsInvite * Group * Command * ClusterInfo * License * Job * System * Plugin * Command2 * IncomingWebhook * OutgoingWebhook * Fix tests * Update traslation * Some fixes * Add missing return * Simplify * Make Config.ToJSONFiltered() return []byte * Make Busy.ToJSON() return []byte * Include error in log * Split logic * [MM-22051] Remove To/From JSON (final) (#18150) * SwitchRequest * PluginEventData * Permalink * PushNotification * SuggestCommand * PluginsResponse * WebSocketMessage * RemoteCluster * SharedChannel * PluginStatuses * InitialLoad * ClusterDiscovery * ClusterStats * MfaSecret * GroupSyncable * SAML * WebSocketRequest * TypingRequest * SecurityBulletin * OAuthApp * IntegrationAction * DataRetention * Preference * FileInfoList * Compliance * Preferences * FileInfoSearchResults * TermsOfService * InstallMarketplacePluginRequest * GitLabUser * UploadSessions * Remove unused helpers * Fix tests * [MM-23280] Fix linting for ToJSON/FromJSON (#18153) * SwitchRequest * PluginEventData * Permalink * PushNotification * SuggestCommand * PluginsResponse * WebSocketMessage * RemoteCluster * SharedChannel * PluginStatuses * InitialLoad * ClusterDiscovery * ClusterStats * MfaSecret * GroupSyncable * SAML * WebSocketRequest * TypingRequest * SecurityBulletin * OAuthApp * IntegrationAction * DataRetention * Preference * FileInfoList * Compliance * Preferences * FileInfoSearchResults * TermsOfService * InstallMarketplacePluginRequest * GitLabUser * UploadSessions * Remove unused helpers * Fix tests * Fix linting for ToJSON/FromJSON * Fix conversions Co-authored-by: Weblate (bot) <hosted@weblate.org> Co-authored-by: JtheBAB <srast@bioc.uzh.ch> Co-authored-by: Kaya Zeren <kayazeren@gmail.com> Co-authored-by: Tóth Csaba // Online ERP Hungary Kft <csaba.toth@online-erp.hu> Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au> Co-authored-by: Nikolai Zahariev <nikolaiz@yahoo.com> Co-authored-by: kaakaa <stooner.hoe@gmail.com> Co-authored-by: aeomin <lin@aeomin.net> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
303 lines
7.7 KiB
Go
303 lines
7.7 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/scrypt"
|
|
)
|
|
|
|
const (
|
|
RemoteOfflineAfterMillis = 1000 * 60 * 5 // 5 minutes
|
|
RemoteNameMinLength = 1
|
|
RemoteNameMaxLength = 64
|
|
)
|
|
|
|
var (
|
|
validRemoteNameChars = regexp.MustCompile(`^[a-zA-Z0-9\.\-\_]+$`)
|
|
)
|
|
|
|
type RemoteCluster struct {
|
|
RemoteId string `json:"remote_id"`
|
|
RemoteTeamId string `json:"remote_team_id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
SiteURL string `json:"site_url"`
|
|
CreateAt int64 `json:"create_at"`
|
|
LastPingAt int64 `json:"last_ping_at"`
|
|
Token string `json:"token"`
|
|
RemoteToken string `json:"remote_token"`
|
|
Topics string `json:"topics"`
|
|
CreatorId string `json:"creator_id"`
|
|
}
|
|
|
|
func (rc *RemoteCluster) PreSave() {
|
|
if rc.RemoteId == "" {
|
|
rc.RemoteId = NewId()
|
|
}
|
|
|
|
if rc.DisplayName == "" {
|
|
rc.DisplayName = rc.Name
|
|
}
|
|
|
|
rc.Name = SanitizeUnicode(rc.Name)
|
|
rc.DisplayName = SanitizeUnicode(rc.DisplayName)
|
|
rc.Name = NormalizeRemoteName(rc.Name)
|
|
|
|
if rc.Token == "" {
|
|
rc.Token = NewId()
|
|
}
|
|
|
|
if rc.CreateAt == 0 {
|
|
rc.CreateAt = GetMillis()
|
|
}
|
|
rc.fixTopics()
|
|
}
|
|
|
|
func (rc *RemoteCluster) IsValid() *AppError {
|
|
if !IsValidId(rc.RemoteId) {
|
|
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "id="+rc.RemoteId, http.StatusBadRequest)
|
|
}
|
|
|
|
if !IsValidRemoteName(rc.Name) {
|
|
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.name.app_error", nil, "name="+rc.Name, http.StatusBadRequest)
|
|
}
|
|
|
|
if rc.CreateAt == 0 {
|
|
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.create_at.app_error", nil, "create_at=0", http.StatusBadRequest)
|
|
}
|
|
|
|
if !IsValidId(rc.CreatorId) {
|
|
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "creator_id="+rc.CreatorId, http.StatusBadRequest)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func IsValidRemoteName(s string) bool {
|
|
if len(s) < RemoteNameMinLength || len(s) > RemoteNameMaxLength {
|
|
return false
|
|
}
|
|
return validRemoteNameChars.MatchString(s)
|
|
}
|
|
|
|
func (rc *RemoteCluster) PreUpdate() {
|
|
if rc.DisplayName == "" {
|
|
rc.DisplayName = rc.Name
|
|
}
|
|
|
|
rc.Name = SanitizeUnicode(rc.Name)
|
|
rc.DisplayName = SanitizeUnicode(rc.DisplayName)
|
|
rc.Name = NormalizeRemoteName(rc.Name)
|
|
rc.fixTopics()
|
|
}
|
|
|
|
func (rc *RemoteCluster) IsOnline() bool {
|
|
return rc.LastPingAt > GetMillis()-RemoteOfflineAfterMillis
|
|
}
|
|
|
|
// fixTopics ensures all topics are separated by one, and only one, space.
|
|
func (rc *RemoteCluster) fixTopics() {
|
|
trimmed := strings.TrimSpace(rc.Topics)
|
|
if trimmed == "" || trimmed == "*" {
|
|
rc.Topics = trimmed
|
|
return
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(" ")
|
|
|
|
ss := strings.Split(rc.Topics, " ")
|
|
for _, c := range ss {
|
|
cc := strings.TrimSpace(c)
|
|
if cc != "" {
|
|
sb.WriteString(cc)
|
|
sb.WriteString(" ")
|
|
}
|
|
}
|
|
rc.Topics = sb.String()
|
|
}
|
|
|
|
func (rc *RemoteCluster) ToRemoteClusterInfo() RemoteClusterInfo {
|
|
return RemoteClusterInfo{
|
|
Name: rc.Name,
|
|
DisplayName: rc.DisplayName,
|
|
CreateAt: rc.CreateAt,
|
|
LastPingAt: rc.LastPingAt,
|
|
}
|
|
}
|
|
|
|
func NormalizeRemoteName(name string) string {
|
|
return strings.ToLower(name)
|
|
}
|
|
|
|
// RemoteClusterInfo provides a subset of RemoteCluster fields suitable for sending to clients.
|
|
type RemoteClusterInfo struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
CreateAt int64 `json:"create_at"`
|
|
LastPingAt int64 `json:"last_ping_at"`
|
|
}
|
|
|
|
// RemoteClusterFrame wraps a `RemoteClusterMsg` with credentials specific to a remote cluster.
|
|
type RemoteClusterFrame struct {
|
|
RemoteId string `json:"remote_id"`
|
|
Msg RemoteClusterMsg `json:"msg"`
|
|
}
|
|
|
|
func (f *RemoteClusterFrame) IsValid() *AppError {
|
|
if !IsValidId(f.RemoteId) {
|
|
return NewAppError("RemoteClusterFrame.IsValid", "api.remote_cluster.invalid_id.app_error", nil, "RemoteId="+f.RemoteId, http.StatusBadRequest)
|
|
}
|
|
|
|
if err := f.Msg.IsValid(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoteClusterMsg represents a message that is sent and received between clusters.
|
|
// These are processed and routed via the RemoteClusters service.
|
|
type RemoteClusterMsg struct {
|
|
Id string `json:"id"`
|
|
Topic string `json:"topic"`
|
|
CreateAt int64 `json:"create_at"`
|
|
Payload json.RawMessage `json:"payload"`
|
|
}
|
|
|
|
func NewRemoteClusterMsg(topic string, payload json.RawMessage) RemoteClusterMsg {
|
|
return RemoteClusterMsg{
|
|
Id: NewId(),
|
|
Topic: topic,
|
|
CreateAt: GetMillis(),
|
|
Payload: payload,
|
|
}
|
|
}
|
|
|
|
func (m RemoteClusterMsg) IsValid() *AppError {
|
|
if !IsValidId(m.Id) {
|
|
return NewAppError("RemoteClusterMsg.IsValid", "api.remote_cluster.invalid_id.app_error", nil, "Id="+m.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if m.Topic == "" {
|
|
return NewAppError("RemoteClusterMsg.IsValid", "api.remote_cluster.invalid_topic.app_error", nil, "Topic empty", http.StatusBadRequest)
|
|
}
|
|
|
|
if len(m.Payload) == 0 {
|
|
return NewAppError("RemoteClusterMsg.IsValid", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": "PayLoad"}, "", http.StatusBadRequest)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoteClusterPing represents a ping that is sent and received between clusters
|
|
// to indicate a connection is alive. This is the payload for a `RemoteClusterMsg`.
|
|
type RemoteClusterPing struct {
|
|
SentAt int64 `json:"sent_at"`
|
|
RecvAt int64 `json:"recv_at"`
|
|
}
|
|
|
|
// RemoteClusterInvite represents an invitation to establish a simple trust with a remote cluster.
|
|
type RemoteClusterInvite struct {
|
|
RemoteId string `json:"remote_id"`
|
|
RemoteTeamId string `json:"remote_team_id"`
|
|
SiteURL string `json:"site_url"`
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
func (rci *RemoteClusterInvite) Encrypt(password string) ([]byte, error) {
|
|
raw, err := json.Marshal(&rci)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create random salt to be prepended to the blob.
|
|
salt := make([]byte, 16)
|
|
if _, err = io.ReadFull(rand.Reader, salt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, err := aes.NewCipher(key[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create random nonce
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// prefix the nonce to the cyphertext so we don't need to keep track of it.
|
|
sealed := gcm.Seal(nonce, nonce, raw, nil)
|
|
|
|
return append(salt, sealed...), nil
|
|
}
|
|
|
|
func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error {
|
|
if len(encrypted) <= 16 {
|
|
return errors.New("invalid length")
|
|
}
|
|
|
|
// first 16 bytes is the salt that was used to derive a key
|
|
salt := encrypted[:16]
|
|
encrypted = encrypted[16:]
|
|
|
|
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
block, err := aes.NewCipher(key[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// nonce was prefixed to the cyphertext when encrypting so we need to extract it.
|
|
nonceSize := gcm.NonceSize()
|
|
nonce, cyphertext := encrypted[:nonceSize], encrypted[nonceSize:]
|
|
|
|
plain, err := gcm.Open(nil, nonce, cyphertext, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// try to unmarshall the decrypted JSON to this invite struct.
|
|
return json.Unmarshal(plain, &rci)
|
|
}
|
|
|
|
// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
|
|
type RemoteClusterQueryFilter struct {
|
|
ExcludeOffline bool
|
|
InChannel string
|
|
NotInChannel string
|
|
Topic string
|
|
CreatorId string
|
|
OnlyConfirmed bool
|
|
}
|