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