From 1f3423796eee06a126d3cab7c276e2d0f169b869 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 30 Oct 2015 13:36:51 -0400 Subject: Validate callback urls on the server and add help text to outgoing webhooks --- model/outgoing_webhook.go | 6 ++++ model/outgoing_webhook_test.go | 5 +++ model/utils.go | 13 ++++++++ .../user_settings/manage_outgoing_hooks.jsx | 39 +++++++++++++--------- .../user_settings/user_settings_integrations.jsx | 2 +- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/model/outgoing_webhook.go b/model/outgoing_webhook.go index 8958dd5b0..9a1b89a85 100644 --- a/model/outgoing_webhook.go +++ b/model/outgoing_webhook.go @@ -100,6 +100,12 @@ func (o *OutgoingWebhook) IsValid() *AppError { return NewAppError("OutgoingWebhook.IsValid", "Invalid callback urls", "") } + for _, callback := range o.CallbackURLs { + if !IsValidHttpUrl(callback) { + return NewAppError("OutgoingWebhook.IsValid", "Invalid callback URLs. Each must be a valid URL and start with http:// or https://", "") + } + } + return nil } diff --git a/model/outgoing_webhook_test.go b/model/outgoing_webhook_test.go index 2ca48c291..0d1cd773e 100644 --- a/model/outgoing_webhook_test.go +++ b/model/outgoing_webhook_test.go @@ -80,6 +80,11 @@ func TestOutgoingWebhookIsValid(t *testing.T) { t.Fatal("should be invalid") } + o.CallbackURLs = []string{"nowhere.com/"} + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + o.CallbackURLs = []string{"http://nowhere.com/"} if err := o.IsValid(); err != nil { t.Fatal(err) diff --git a/model/utils.go b/model/utils.go index bb0669df7..681ade870 100644 --- a/model/utils.go +++ b/model/utils.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net/mail" + "net/url" "regexp" "strings" "time" @@ -301,3 +302,15 @@ var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a- var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+(?:\.[A-Za-z0-9]{3,})?)`) var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true} + +func IsValidHttpUrl(rawUrl string) bool { + if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { + return false + } + + if _, err := url.ParseRequestURI(rawUrl); err != nil { + return false + } + + return true +} diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx index 9b0701583..93be988d1 100644 --- a/web/react/components/user_settings/manage_outgoing_hooks.jsx +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -1,10 +1,12 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var Client = require('../../utils/client.jsx'); -var Constants = require('../../utils/constants.jsx'); -var ChannelStore = require('../../stores/channel_store.jsx'); -var LoadingScreen = require('../loading_screen.jsx'); +const LoadingScreen = require('../loading_screen.jsx'); + +const ChannelStore = require('../../stores/channel_store.jsx'); + +const Client = require('../../utils/client.jsx'); +const Constants = require('../../utils/constants.jsx'); export default class ManageOutgoingHooks extends React.Component { constructor() { @@ -44,10 +46,10 @@ export default class ManageOutgoingHooks extends React.Component { hooks = []; } hooks.push(data); - this.setState({hooks, serverError: null, channelId: '', triggerWords: '', callbackURLs: ''}); + this.setState({hooks, addError: null, channelId: '', triggerWords: '', callbackURLs: ''}); }, (err) => { - this.setState({serverError: err}); + this.setState({addError: err.message}); } ); } @@ -74,7 +76,7 @@ export default class ManageOutgoingHooks extends React.Component { this.setState({hooks}); }, (err) => { - this.setState({serverError: err}); + this.setState({editError: err.message}); } ); } @@ -93,10 +95,10 @@ export default class ManageOutgoingHooks extends React.Component { } } - this.setState({hooks, serverError: null}); + this.setState({hooks, editError: null}); }, (err) => { - this.setState({serverError: err}); + this.setState({editError: err.message}); } ); } @@ -104,11 +106,11 @@ export default class ManageOutgoingHooks extends React.Component { Client.listOutgoingHooks( (data) => { if (data) { - this.setState({hooks: data, getHooksComplete: true, serverError: null}); + this.setState({hooks: data, getHooksComplete: true, editError: null}); } }, (err) => { - this.setState({serverError: err}); + this.setState({editError: err.message}); } ); } @@ -122,9 +124,13 @@ export default class ManageOutgoingHooks extends React.Component { this.setState({callbackURLs: e.target.value}); } render() { - let serverError; - if (this.state.serverError) { - serverError = ; + let addError; + if (this.state.addError) { + addError = ; + } + let editError; + if (this.state.editError) { + addError = ; } const channels = ChannelStore.getAll(); @@ -234,6 +240,7 @@ export default class ManageOutgoingHooks extends React.Component { return (
+ {'Create webhooks to send new message events to an external integration. Please see '}{'http://mattermost.org/webhooks'} {' to learn more.'}
@@ -274,10 +281,11 @@ export default class ManageOutgoingHooks extends React.Component { resize={false} rows={3} onChange={this.updateCallbackURLs} + placeholder='Each URL must start with http:// or https://' />
{'New line separated URLs that will receive the HTTP POST event'}
- {serverError} + {addError}
{existingHooks} + {editError} ); } diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx index 9bee74343..4a9915a1f 100644 --- a/web/react/components/user_settings/user_settings_integrations.jsx +++ b/web/react/components/user_settings/user_settings_integrations.jsx @@ -56,7 +56,7 @@ export default class UserSettingsIntegrationsTab extends React.Component { { this.updateSection('incoming-hooks'); }} -- cgit v1.2.3-1-g7c22