diff --git a/config/config.json b/config/config.json index 48514e1a46..b14175372f 100644 --- a/config/config.json +++ b/config/config.json @@ -6,6 +6,8 @@ "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": true, + "EnablePostUsernameOverride": false, + "EnablePostIconOverride": false, "EnableTesting": false }, "TeamSettings": { @@ -87,4 +89,4 @@ "TokenEndpoint": "", "UserApiEndpoint": "" } -} \ No newline at end of file +} diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json index 2611a63ce5..ef91a21ea7 100644 --- a/docker/dev/config_docker.json +++ b/docker/dev/config_docker.json @@ -6,6 +6,8 @@ "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": true, + "EnablePostUsernameOverride": false, + "EnablePostIconOverride": false, "EnableTesting": false }, "TeamSettings": { diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json index 2611a63ce5..ef91a21ea7 100644 --- a/docker/local/config_docker.json +++ b/docker/local/config_docker.json @@ -6,6 +6,8 @@ "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": true, + "EnablePostUsernameOverride": false, + "EnablePostIconOverride": false, "EnableTesting": false }, "TeamSettings": { diff --git a/model/config.go b/model/config.go index 35ceb7f4ab..c67b36063b 100644 --- a/model/config.go +++ b/model/config.go @@ -29,6 +29,8 @@ type ServiceSettings struct { GoogleDeveloperKey string EnableOAuthServiceProvider bool EnableIncomingWebhooks bool + EnablePostUsernameOverride bool + EnablePostIconOverride bool EnableTesting bool } diff --git a/utils/config.go b/utils/config.go index 3218211e3a..44c4c43aff 100644 --- a/utils/config.go +++ b/utils/config.go @@ -184,6 +184,8 @@ func getClientProperties(c *model.Config) map[string]string { props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks) + props["EnablePostUsernameOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostUsernameOverride) + props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride) props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications) props["EnableSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.EnableSignUpWithEmail) diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 245ffa871c..b2d1b7b4d4 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -37,6 +37,8 @@ export default class ServiceSettings extends React.Component { config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); //config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked; + config.ServiceSettings.EnablePostUsernameOverride = React.findDOMNode(this.refs.EnablePostUsernameOverride).checked; + config.ServiceSettings.EnablePostIconOverride = React.findDOMNode(this.refs.EnablePostIconOverride).checked; config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked; var MaximumLoginAttempts = 10; @@ -199,7 +201,73 @@ export default class ServiceSettings extends React.Component { /> {'false'} -

{'When true, incoming webhooks will be allowed.'}

+

{'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'}

+ + + +
+ +
+ + +

{'When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'}

+
+
+ +
+ +
+ + +

{'When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'}

diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 9127f00de1..ac9c9252e7 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -158,11 +158,18 @@ export default class Post extends React.Component { var profilePic = null; if (!this.props.hideProfilePic) { + let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp; + if (post.props && post.props.from_webhook && global.window.config.EnablePostIconOverride === 'true') { + if (post.props.override_icon_url) { + src = post.props.override_icon_url; + } + } + profilePic = (
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx index 9dc525e036..dd79b3e369 100644 --- a/web/react/components/post_header.jsx +++ b/web/react/components/post_header.jsx @@ -12,9 +12,27 @@ export default class PostHeader extends React.Component { render() { var post = this.props.post; + let userProfile = ; + let botIndicator; + + if (post.props && post.props.from_webhook) { + if (post.props.override_username && global.window.config.EnablePostUsernameOverride === 'true') { + userProfile = ( + + ); + } + + botIndicator =
  • {'BOT'}
  • ; + } + return (
      -
    • +
    • {userProfile}
    • + {botIndicator}
    • {name}
    ; + } + var dataContent = ''; if (!global.window.config.ShowEmailAddress === 'true') { dataContent += '
    Email not shared
    '; @@ -79,9 +85,11 @@ export default class UserProfile extends React.Component { UserProfile.defaultProps = { userId: '', - overwriteName: '' + overwriteName: '', + disablePopover: false }; UserProfile.propTypes = { userId: React.PropTypes.string, - overwriteName: React.PropTypes.string + overwriteName: React.PropTypes.string, + disablePopover: React.PropTypes.bool }; diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 7532875d64..8bf4b05345 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -509,3 +509,11 @@ body.ios { } } } + +.bot-indicator { + background-color: lightgrey; + border-radius:2px; + padding-left:2px; + padding-right:2px; + font-family:"Courier New" +} diff --git a/web/web.go b/web/web.go index e440699b2a..a1bbf5a818 100644 --- a/web/web.go +++ b/web/web.go @@ -884,6 +884,12 @@ func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) { } func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) { + if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks { + c.Err = model.NewAppError("incomingWebhook", "Incoming webhooks have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + params := mux.Vars(r) id := params["id"] @@ -906,6 +912,9 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) { channelName := props["channel"] + overrideUsername := props["username"] + overrideIconUrl := props["icon_url"] + var hook *model.IncomingWebhook if result := <-hchan; result.Err != nil { c.Err = model.NewAppError("incomingWebhook", "Invalid webhook", "err="+result.Err.Message) @@ -951,6 +960,15 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) { pchan := api.Srv.Store.Channel().CheckPermissionsTo(hook.TeamId, channel.Id, hook.UserId) post := &model.Post{UserId: hook.UserId, ChannelId: channel.Id, Message: text} + post.AddProp("from_webhook", "true") + + if len(overrideUsername) != 0 && utils.Cfg.ServiceSettings.EnablePostUsernameOverride { + post.AddProp("override_username", overrideUsername) + } + + if len(overrideIconUrl) != 0 && utils.Cfg.ServiceSettings.EnablePostIconOverride { + post.AddProp("override_icon_url", overrideIconUrl) + } if !c.HasPermissionsToChannel(pchan, "createIncomingHook") && channel.Type != model.CHANNEL_OPEN { c.Err = model.NewAppError("incomingWebhook", "Inappropriate channel permissions", "")