Merge pull request #914 from mattermost/plt-297
PLT-297 Add ability to override username and icon for posts from incoming webhooks.
Этот коммит содержится в:
@@ -6,6 +6,8 @@
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"EnableIncomingWebhooks": true,
|
||||
"EnablePostUsernameOverride": false,
|
||||
"EnablePostIconOverride": false,
|
||||
"EnableTesting": false
|
||||
},
|
||||
"TeamSettings": {
|
||||
@@ -87,4 +89,4 @@
|
||||
"TokenEndpoint": "",
|
||||
"UserApiEndpoint": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"EnableIncomingWebhooks": true,
|
||||
"EnablePostUsernameOverride": false,
|
||||
"EnablePostIconOverride": false,
|
||||
"EnableTesting": false
|
||||
},
|
||||
"TeamSettings": {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"EnableIncomingWebhooks": true,
|
||||
"EnablePostUsernameOverride": false,
|
||||
"EnablePostIconOverride": false,
|
||||
"EnableTesting": false
|
||||
},
|
||||
"TeamSettings": {
|
||||
|
||||
@@ -29,6 +29,8 @@ type ServiceSettings struct {
|
||||
GoogleDeveloperKey string
|
||||
EnableOAuthServiceProvider bool
|
||||
EnableIncomingWebhooks bool
|
||||
EnablePostUsernameOverride bool
|
||||
EnablePostIconOverride bool
|
||||
EnableTesting bool
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'}
|
||||
</label>
|
||||
<p className='help-text'>{'When true, incoming webhooks will be allowed.'}</p>
|
||||
<p className='help-text'>{'When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='EnablePostUsernameOverride'
|
||||
>
|
||||
{'Enable Overriding Usernames from Webhooks: '}
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='EnablePostUsernameOverride'
|
||||
value='true'
|
||||
ref='EnablePostUsernameOverride'
|
||||
defaultChecked={this.props.config.ServiceSettings.EnablePostUsernameOverride}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{'true'}
|
||||
</label>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='EnablePostUsernameOverride'
|
||||
value='false'
|
||||
defaultChecked={!this.props.config.ServiceSettings.EnablePostUsernameOverride}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{'false'}
|
||||
</label>
|
||||
<p className='help-text'>{'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.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='EnablePostIconOverride'
|
||||
>
|
||||
{'Enable Overriding Icon from Webhooks: '}
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='EnablePostIconOverride'
|
||||
value='true'
|
||||
ref='EnablePostIconOverride'
|
||||
defaultChecked={this.props.config.ServiceSettings.EnablePostIconOverride}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{'true'}
|
||||
</label>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='EnablePostIconOverride'
|
||||
value='false'
|
||||
defaultChecked={!this.props.config.ServiceSettings.EnablePostIconOverride}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{'false'}
|
||||
</label>
|
||||
<p className='help-text'>{'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.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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 = (
|
||||
<div className='post-profile-img__container'>
|
||||
<img
|
||||
className='post-profile-img'
|
||||
src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
|
||||
src={src}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
|
||||
@@ -12,9 +12,27 @@ export default class PostHeader extends React.Component {
|
||||
render() {
|
||||
var post = this.props.post;
|
||||
|
||||
let userProfile = <UserProfile userId={post.user_id} />;
|
||||
let botIndicator;
|
||||
|
||||
if (post.props && post.props.from_webhook) {
|
||||
if (post.props.override_username && global.window.config.EnablePostUsernameOverride === 'true') {
|
||||
userProfile = (
|
||||
<UserProfile
|
||||
userId={post.user_id}
|
||||
overwriteName={post.props.override_username}
|
||||
disablePopover={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
botIndicator = <li className='post-header-col post-header__name bot-indicator'>{'BOT'}</li>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className='post-header post-header-post'>
|
||||
<li className='post-header-col post-header__name'><strong><UserProfile userId={post.user_id} /></strong></li>
|
||||
<li className='post-header-col post-header__name'><strong>{userProfile}</strong></li>
|
||||
{botIndicator}
|
||||
<li className='post-info--hidden'>
|
||||
<PostInfo
|
||||
post={post}
|
||||
|
||||
@@ -516,8 +516,19 @@ export default class PostList extends React.Component {
|
||||
|
||||
sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
|
||||
|
||||
// we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
|
||||
hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post);
|
||||
// hide the profile pic if:
|
||||
// the previous post was made by the same user as the current post,
|
||||
// the previous post is not a comment,
|
||||
// the current post is not a comment,
|
||||
// the current post is not from a webhook
|
||||
// and the previous post is not from a webhook
|
||||
if ((prevPost.user_id === post.user_id) &&
|
||||
!utils.isComment(prevPost) &&
|
||||
!utils.isComment(post) &&
|
||||
(!post.props || !post.props.from_webhook) &&
|
||||
(!prevPost.props || !prevPost.props.from_webhook)) {
|
||||
hideProfilePic = true;
|
||||
}
|
||||
}
|
||||
|
||||
// check if it's the last comment in a consecutive string of comments on the same post
|
||||
|
||||
@@ -31,8 +31,10 @@ export default class UserProfile extends React.Component {
|
||||
}
|
||||
componentDidMount() {
|
||||
UserStore.addChangeListener(this.onChange);
|
||||
$('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}});
|
||||
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
|
||||
if (!this.props.disablePopover) {
|
||||
$('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}});
|
||||
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
UserStore.removeChangeListener(this.onChange);
|
||||
@@ -56,6 +58,10 @@ export default class UserProfile extends React.Component {
|
||||
name = this.props.overwriteName;
|
||||
}
|
||||
|
||||
if (this.props.disablePopover) {
|
||||
return <div>{name}</div>;
|
||||
}
|
||||
|
||||
var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />';
|
||||
if (!global.window.config.ShowEmailAddress === 'true') {
|
||||
dataContent += '<div class="text-nowrap">Email not shared</div>';
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -509,3 +509,11 @@ body.ios {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-indicator {
|
||||
background-color: lightgrey;
|
||||
border-radius:2px;
|
||||
padding-left:2px;
|
||||
padding-right:2px;
|
||||
font-family:"Courier New"
|
||||
}
|
||||
|
||||
18
web/web.go
18
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", "")
|
||||
|
||||
Ссылка в новой задаче
Block a user