mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-07-04 20:37:44 +00:00
Use mattermost v5 module (#1192)
This commit is contained in:
525
vendor/github.com/mattermost/mattermost-server/v5/model/integration_action.go
generated
vendored
Normal file
525
vendor/github.com/mattermost/mattermost-server/v5/model/integration_action.go
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user