[MM-53269] Add configuration setting for integration requests timeout (#23805)

Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com>
Этот коммит содержится в:
Ben Schumacher
2023-12-18 16:07:00 +01:00
коммит произвёл GitHub
родитель 7874daa6f7
Коммит 1fdddfe678
20 изменённых файлов: 343 добавлений и 136 удалений

Просмотреть файл

@@ -214,6 +214,8 @@ const (
DataRetentionSettingsDefaultTimeBetweenBatchesMilliseconds = 100
DataRetentionSettingsDefaultRetentionIdsBatchSize = 100
OutgoingIntegrationRequestsDefaultTimeout = 30
PluginSettingsDefaultDirectory = "./plugins"
PluginSettingsDefaultClientDirectory = "./client/plugins"
PluginSettingsDefaultEnableMarketplace = true
@@ -309,6 +311,7 @@ type ServiceSettings struct {
EnableIncomingWebhooks *bool `access:"integrations_integration_management"`
EnableOutgoingWebhooks *bool `access:"integrations_integration_management"`
EnableCommands *bool `access:"integrations_integration_management"`
OutgoingIntegrationRequestsTimeout *int64 `access:"integrations_integration_management"` // In seconds.
EnablePostUsernameOverride *bool `access:"integrations_integration_management"`
EnablePostIconOverride *bool `access:"integrations_integration_management"`
GoogleDeveloperKey *string `access:"site_posts,write_restrictable,cloud_restrictable"`
@@ -509,6 +512,10 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
s.EnableOutgoingWebhooks = NewBool(true)
}
if s.OutgoingIntegrationRequestsTimeout == nil {
s.OutgoingIntegrationRequestsTimeout = NewInt64(OutgoingIntegrationRequestsDefaultTimeout)
}
if s.ConnectionSecurity == nil {
s.ConnectionSecurity = NewString("")
}
@@ -4011,6 +4018,10 @@ func (s *ServiceSettings) isValid() *AppError {
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
}
if *s.OutgoingIntegrationRequestsTimeout <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.outgoing_integrations_request_timeout.app_error", nil, "", http.StatusBadRequest)
}
if *s.ExperimentalGroupUnreadChannels != GroupUnreadChannelsDisabled &&
*s.ExperimentalGroupUnreadChannels != GroupUnreadChannelsDefaultOn &&
*s.ExperimentalGroupUnreadChannels != GroupUnreadChannelsDefaultOff {

Просмотреть файл

@@ -74,6 +74,47 @@ func TestConfigEmptySiteName(t *testing.T) {
require.Equal(t, *c1.TeamSettings.SiteName, TeamSettingsDefaultSiteName)
}
func TestServiceSettingsIsValid(t *testing.T) {
for name, test := range map[string]struct {
ServiceSettings ServiceSettings
ExpectError bool
}{
"empty": {
ServiceSettings: ServiceSettings{},
ExpectError: false,
},
"OutgoingIntegrationRequestsTimeout is negative": {
ServiceSettings: ServiceSettings{
OutgoingIntegrationRequestsTimeout: NewInt64(-1),
},
ExpectError: true,
},
"OutgoingIntegrationRequestsTimeout is zero": {
ServiceSettings: ServiceSettings{
OutgoingIntegrationRequestsTimeout: NewInt64(0),
},
ExpectError: true,
},
"OutgoingIntegrationRequestsTimeout is positiv": {
ServiceSettings: ServiceSettings{
OutgoingIntegrationRequestsTimeout: NewInt64(1),
},
ExpectError: false,
},
} {
t.Run(name, func(t *testing.T) {
test.ServiceSettings.SetDefaults(false)
appErr := test.ServiceSettings.isValid()
if test.ExpectError {
assert.NotNil(t, appErr)
} else {
assert.Nil(t, appErr)
}
})
}
}
func TestConfigEnableDeveloper(t *testing.T) {
testCases := []struct {
Description string

Просмотреть файл

@@ -19,12 +19,12 @@ import (
"reflect"
"strconv"
"strings"
"time"
)
const (
PostActionTypeButton = "button"
PostActionTypeSelect = "select"
InteractiveDialogTriggerTimeoutMilliseconds = 3000
PostActionTypeButton = "button"
PostActionTypeSelect = "select"
)
var PostActionRetainPropKeys = []string{"from_webhook", "override_username", "override_icon_url"}
@@ -280,7 +280,7 @@ func (r *PostActionIntegrationRequest) GenerateTriggerId(s crypto.Signer) (strin
return clientTriggerId, triggerId, nil
}
func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, string, *AppError) {
func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey, timeout time.Duration) (string, string, *AppError) {
triggerIdBytes, err := base64.StdEncoding.DecodeString(triggerId)
if err != nil {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed", nil, "", http.StatusBadRequest).Wrap(err)
@@ -296,9 +296,8 @@ func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, st
timestampStr := split[2]
timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
now := GetMillis()
if now-timestamp > InteractiveDialogTriggerTimeoutMilliseconds {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.expired", map[string]any{"Seconds": InteractiveDialogTriggerTimeoutMilliseconds / 1000}, "", http.StatusBadRequest)
if time.Since(time.UnixMilli(timestamp)) > timeout {
return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.expired", map[string]any{"Duration": timeout.String()}, "", http.StatusBadRequest)
}
signature, err := base64.StdEncoding.DecodeString(split[3])
@@ -327,8 +326,8 @@ func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, st
return clientTriggerId, userId, nil
}
func (r *OpenDialogRequest) DecodeAndVerifyTriggerId(s *ecdsa.PrivateKey) (string, string, *AppError) {
return DecodeAndVerifyTriggerId(r.TriggerId, s)
func (r *OpenDialogRequest) DecodeAndVerifyTriggerId(s *ecdsa.PrivateKey, timeout time.Duration) (string, string, *AppError) {
return DecodeAndVerifyTriggerId(r.TriggerId, s, timeout)
}
func (o *Post) StripActionIntegrations() {

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"crypto/rand"
"encoding/base64"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -22,7 +23,7 @@ func TestTriggerIdDecodeAndVerification(t *testing.T) {
userId := NewId()
clientTriggerId, triggerId, appErr := GenerateTriggerId(userId, key)
require.Nil(t, appErr)
decodedClientTriggerId, decodedUserId, appErr := DecodeAndVerifyTriggerId(triggerId, key)
decodedClientTriggerId, decodedUserId, appErr := DecodeAndVerifyTriggerId(triggerId, key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
assert.Nil(t, appErr)
assert.Equal(t, clientTriggerId, decodedClientTriggerId)
assert.Equal(t, userId, decodedUserId)
@@ -35,38 +36,38 @@ func TestTriggerIdDecodeAndVerification(t *testing.T) {
clientTriggerId, triggerId, appErr := actionReq.GenerateTriggerId(key)
require.Nil(t, appErr)
dialogReq := &OpenDialogRequest{TriggerId: triggerId}
decodedClientTriggerId, decodedUserId, appErr := dialogReq.DecodeAndVerifyTriggerId(key)
decodedClientTriggerId, decodedUserId, appErr := dialogReq.DecodeAndVerifyTriggerId(key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
assert.Nil(t, appErr)
assert.Equal(t, clientTriggerId, decodedClientTriggerId)
assert.Equal(t, actionReq.UserId, decodedUserId)
})
t.Run("should fail on base64 decode", func(t *testing.T) {
_, _, appErr := DecodeAndVerifyTriggerId("junk!", key)
_, _, appErr := DecodeAndVerifyTriggerId("junk!", key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
require.NotNil(t, appErr)
assert.Equal(t, "interactive_message.decode_trigger_id.base64_decode_failed", appErr.Id)
})
t.Run("should fail on trigger parsing", func(t *testing.T) {
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("junk!")), key)
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("junk!")), key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
require.NotNil(t, appErr)
assert.Equal(t, "interactive_message.decode_trigger_id.missing_data", appErr.Id)
})
t.Run("should fail on expired timestamp", func(t *testing.T) {
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:1234567890:junksignature")), key)
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:1234567890:junksignature")), key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
require.NotNil(t, appErr)
assert.Equal(t, "interactive_message.decode_trigger_id.expired", appErr.Id)
})
t.Run("should fail on base64 decoding signature", func(t *testing.T) {
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:12345678900000:junk!")), key)
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:12345678900000:junk!")), key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
require.NotNil(t, appErr)
assert.Equal(t, "interactive_message.decode_trigger_id.base64_decode_failed_signature", appErr.Id)
})
t.Run("should fail on bad signature", func(t *testing.T) {
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:12345678900000:junk")), key)
_, _, appErr := DecodeAndVerifyTriggerId(base64.StdEncoding.EncodeToString([]byte("some-trigger-id:some-user-id:12345678900000:junk")), key, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
require.NotNil(t, appErr)
assert.Equal(t, "interactive_message.decode_trigger_id.signature_decode_failed", appErr.Id)
})
@@ -76,7 +77,7 @@ func TestTriggerIdDecodeAndVerification(t *testing.T) {
require.Nil(t, appErr)
newKey, keyErr := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, keyErr)
_, _, appErr = DecodeAndVerifyTriggerId(triggerId, newKey)
_, _, appErr = DecodeAndVerifyTriggerId(triggerId, newKey, OutgoingIntegrationRequestsDefaultTimeout*time.Second)
require.NotNil(t, appErr)
assert.Equal(t, "interactive_message.decode_trigger_id.verify_signature_failed", appErr.Id)
})