github.com/jfrerich/mattermost-server@v5.8.0-rc2+incompatible/model/integration_action.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package model
     5  
     6  import (
     7  	"crypto"
     8  	"crypto/ecdsa"
     9  	"crypto/rand"
    10  	"encoding/asn1"
    11  	"encoding/base64"
    12  	"encoding/json"
    13  	"io"
    14  	"math/big"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  const (
    21  	POST_ACTION_TYPE_BUTTON                         = "button"
    22  	POST_ACTION_TYPE_SELECT                         = "select"
    23  	INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS = 3000
    24  )
    25  
    26  type DoPostActionRequest struct {
    27  	SelectedOption string `json:"selected_option"`
    28  }
    29  
    30  type PostAction struct {
    31  	Id          string                 `json:"id"`
    32  	Name        string                 `json:"name"`
    33  	Type        string                 `json:"type"`
    34  	DataSource  string                 `json:"data_source"`
    35  	Options     []*PostActionOptions   `json:"options"`
    36  	Integration *PostActionIntegration `json:"integration,omitempty"`
    37  }
    38  
    39  type PostActionOptions struct {
    40  	Text  string `json:"text"`
    41  	Value string `json:"value"`
    42  }
    43  
    44  type PostActionIntegration struct {
    45  	URL     string                 `json:"url,omitempty"`
    46  	Context map[string]interface{} `json:"context,omitempty"`
    47  }
    48  
    49  type PostActionIntegrationRequest struct {
    50  	UserId     string                 `json:"user_id"`
    51  	ChannelId  string                 `json:"channel_id"`
    52  	TeamId     string                 `json:"team_id"`
    53  	PostId     string                 `json:"post_id"`
    54  	TriggerId  string                 `json:"trigger_id"`
    55  	Type       string                 `json:"type"`
    56  	DataSource string                 `json:"data_source"`
    57  	Context    map[string]interface{} `json:"context,omitempty"`
    58  }
    59  
    60  type PostActionIntegrationResponse struct {
    61  	Update        *Post  `json:"update"`
    62  	EphemeralText string `json:"ephemeral_text"`
    63  }
    64  
    65  type PostActionAPIResponse struct {
    66  	Status    string `json:"status"` // needed to maintain backwards compatibility
    67  	TriggerId string `json:"trigger_id"`
    68  }
    69  
    70  type Dialog struct {
    71  	CallbackId     string          `json:"callback_id"`
    72  	Title          string          `json:"title"`
    73  	IconURL        string          `json:"icon_url"`
    74  	Elements       []DialogElement `json:"elements"`
    75  	SubmitLabel    string          `json:"submit_label"`
    76  	NotifyOnCancel bool            `json:"notify_on_cancel"`
    77  	State          string          `json:"state"`
    78  }
    79  
    80  type DialogElement struct {
    81  	DisplayName string               `json:"display_name"`
    82  	Name        string               `json:"name"`
    83  	Type        string               `json:"type"`
    84  	SubType     string               `json:"subtype"`
    85  	Default     string               `json:"default"`
    86  	Placeholder string               `json:"placeholder"`
    87  	HelpText    string               `json:"help_text"`
    88  	Optional    bool                 `json:"optional"`
    89  	MinLength   int                  `json:"min_length"`
    90  	MaxLength   int                  `json:"max_length"`
    91  	DataSource  string               `json:"data_source"`
    92  	Options     []*PostActionOptions `json:"options"`
    93  }
    94  
    95  type OpenDialogRequest struct {
    96  	TriggerId string `json:"trigger_id"`
    97  	URL       string `json:"url"`
    98  	Dialog    Dialog `json:"dialog"`
    99  }
   100  
   101  type SubmitDialogRequest struct {
   102  	Type       string                 `json:"type"`
   103  	URL        string                 `json:"url,omitempty"`
   104  	CallbackId string                 `json:"callback_id"`
   105  	State      string                 `json:"state"`
   106  	UserId     string                 `json:"user_id"`
   107  	ChannelId  string                 `json:"channel_id"`
   108  	TeamId     string                 `json:"team_id"`
   109  	Submission map[string]interface{} `json:"submission"`
   110  	Cancelled  bool                   `json:"cancelled"`
   111  }
   112  
   113  type SubmitDialogResponse struct {
   114  	Errors map[string]string `json:"errors,omitempty"`
   115  }
   116  
   117  func GenerateTriggerId(userId string, s crypto.Signer) (string, string, *AppError) {
   118  	clientTriggerId := NewId()
   119  	triggerData := strings.Join([]string{clientTriggerId, userId, strconv.FormatInt(GetMillis(), 10)}, ":") + ":"
   120  
   121  	h := crypto.SHA256
   122  	sum := h.New()
   123  	sum.Write([]byte(triggerData))
   124  	signature, err := s.Sign(rand.Reader, sum.Sum(nil), h)
   125  	if err != nil {
   126  		return "", "", NewAppError("GenerateTriggerId", "interactive_message.generate_trigger_id.signing_failed", nil, err.Error(), http.StatusInternalServerError)
   127  	}
   128  
   129  	base64Sig := base64.StdEncoding.EncodeToString(signature)
   130  
   131  	triggerId := base64.StdEncoding.EncodeToString([]byte(triggerData + base64Sig))
   132  	return clientTriggerId, triggerId, nil
   133  }
   134  
   135  func (r *PostActionIntegrationRequest) GenerateTriggerId(s crypto.Signer) (string, string, *AppError) {
   136  	clientTriggerId, triggerId, err := GenerateTriggerId(r.UserId, s)
   137  	if err != nil {
   138  		return "", "", err
   139  	}
   140  
   141  	r.TriggerId = triggerId
   142  	return clientTriggerId, triggerId, nil
   143  }
   144  
   145  func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, string, *AppError) {
   146  	triggerIdBytes, err := base64.StdEncoding.DecodeString(triggerId)
   147  	if err != nil {
   148  		return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed", nil, err.Error(), http.StatusBadRequest)
   149  	}
   150  
   151  	split := strings.Split(string(triggerIdBytes), ":")
   152  	if len(split) != 4 {
   153  		return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.missing_data", nil, "", http.StatusBadRequest)
   154  	}
   155  
   156  	clientTriggerId := split[0]
   157  	userId := split[1]
   158  	timestampStr := split[2]
   159  	timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
   160  
   161  	now := GetMillis()
   162  	if now-timestamp > INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS {
   163  		return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.expired", map[string]interface{}{"Seconds": INTERACTIVE_DIALOG_TRIGGER_TIMEOUT_MILLISECONDS / 1000}, "", http.StatusBadRequest)
   164  	}
   165  
   166  	signature, err := base64.StdEncoding.DecodeString(split[3])
   167  	if err != nil {
   168  		return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed_signature", nil, err.Error(), http.StatusBadRequest)
   169  	}
   170  
   171  	var esig struct {
   172  		R, S *big.Int
   173  	}
   174  
   175  	if _, err := asn1.Unmarshal([]byte(signature), &esig); err != nil {
   176  		return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.signature_decode_failed", nil, err.Error(), http.StatusBadRequest)
   177  	}
   178  
   179  	triggerData := strings.Join([]string{clientTriggerId, userId, timestampStr}, ":") + ":"
   180  
   181  	h := crypto.SHA256
   182  	sum := h.New()
   183  	sum.Write([]byte(triggerData))
   184  
   185  	if !ecdsa.Verify(&s.PublicKey, sum.Sum(nil), esig.R, esig.S) {
   186  		return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.verify_signature_failed", nil, "", http.StatusBadRequest)
   187  	}
   188  
   189  	return clientTriggerId, userId, nil
   190  }
   191  
   192  func (r *OpenDialogRequest) DecodeAndVerifyTriggerId(s *ecdsa.PrivateKey) (string, string, *AppError) {
   193  	return DecodeAndVerifyTriggerId(r.TriggerId, s)
   194  }
   195  
   196  func (r *PostActionIntegrationRequest) ToJson() []byte {
   197  	b, _ := json.Marshal(r)
   198  	return b
   199  }
   200  
   201  func PostActionIntegrationRequestFromJson(data io.Reader) *PostActionIntegrationRequest {
   202  	var o *PostActionIntegrationRequest
   203  	err := json.NewDecoder(data).Decode(&o)
   204  	if err != nil {
   205  		return nil
   206  	}
   207  	return o
   208  }
   209  
   210  func (r *PostActionIntegrationResponse) ToJson() []byte {
   211  	b, _ := json.Marshal(r)
   212  	return b
   213  }
   214  
   215  func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse {
   216  	var o *PostActionIntegrationResponse
   217  	err := json.NewDecoder(data).Decode(&o)
   218  	if err != nil {
   219  		return nil
   220  	}
   221  	return o
   222  }
   223  
   224  func SubmitDialogRequestFromJson(data io.Reader) *SubmitDialogRequest {
   225  	var o *SubmitDialogRequest
   226  	err := json.NewDecoder(data).Decode(&o)
   227  	if err != nil {
   228  		return nil
   229  	}
   230  	return o
   231  }
   232  
   233  func (r *SubmitDialogRequest) ToJson() []byte {
   234  	b, _ := json.Marshal(r)
   235  	return b
   236  }
   237  
   238  func SubmitDialogResponseFromJson(data io.Reader) *SubmitDialogResponse {
   239  	var o *SubmitDialogResponse
   240  	err := json.NewDecoder(data).Decode(&o)
   241  	if err != nil {
   242  		return nil
   243  	}
   244  	return o
   245  }
   246  
   247  func (r *SubmitDialogResponse) ToJson() []byte {
   248  	b, _ := json.Marshal(r)
   249  	return b
   250  }
   251  
   252  func (o *Post) StripActionIntegrations() {
   253  	attachments := o.Attachments()
   254  	if o.Props["attachments"] != nil {
   255  		o.Props["attachments"] = attachments
   256  	}
   257  	for _, attachment := range attachments {
   258  		for _, action := range attachment.Actions {
   259  			action.Integration = nil
   260  		}
   261  	}
   262  }
   263  
   264  func (o *Post) GetAction(id string) *PostAction {
   265  	for _, attachment := range o.Attachments() {
   266  		for _, action := range attachment.Actions {
   267  			if action.Id == id {
   268  				return action
   269  			}
   270  		}
   271  	}
   272  	return nil
   273  }
   274  
   275  func (o *Post) GenerateActionIds() {
   276  	if o.Props["attachments"] != nil {
   277  		o.Props["attachments"] = o.Attachments()
   278  	}
   279  	if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
   280  		for _, attachment := range attachments {
   281  			for _, action := range attachment.Actions {
   282  				if action.Id == "" {
   283  					action.Id = NewId()
   284  				}
   285  			}
   286  		}
   287  	}
   288  }
   289  
   290  func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest {
   291  	var o *DoPostActionRequest
   292  	json.NewDecoder(data).Decode(&o)
   293  	return o
   294  }