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 }