From 7952b5aeb7ab51f79a0453f75a6a60746f47b4e3 Mon Sep 17 00:00:00 2001
From: Farhan Munshi <3207297+fm2munsh@users.noreply.github.com>
Date: Wed, 26 Feb 2020 20:41:47 -0500
Subject: [PATCH] [MM-21923] Enforce channel mentions permission (#3875)
* MM-21923 Enforce channel mentions permission by hiding it from autocomplete and disabling @mention confirmation modal
Add tests for post_textbox channel mentions
* MM-21923 Set use channel mentions to true if server version is not minimum required
* MM-21923 Point to specific redux hash and update permission check to be easier to read
* MM-21923 Fix currentChannel.id
---
.../autocomplete/at_mention/at_mention.js | 3 +-
.../autocomplete/at_mention/index.js | 16 +++++++++
app/components/post_textbox/index.js | 16 ++++++++-
.../post_textbox/post_textbox.test.js | 35 +++++++++++++++++++
.../post_textbox/post_textbox_base.js | 3 +-
5 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/app/components/autocomplete/at_mention/at_mention.js b/app/components/autocomplete/at_mention/at_mention.js
index c8478c2ae..c3b00754c 100644
--- a/app/components/autocomplete/at_mention/at_mention.js
+++ b/app/components/autocomplete/at_mention/at_mention.js
@@ -37,6 +37,7 @@ export default class AtMention extends PureComponent {
value: PropTypes.string,
isLandscape: PropTypes.bool.isRequired,
nestedScrollEnabled: PropTypes.bool,
+ useChannelMentions: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -100,7 +101,7 @@ export default class AtMention extends PureComponent {
});
}
- if (this.checkSpecialMentions(matchTerm)) {
+ if (this.props.useChannelMentions && this.checkSpecialMentions(matchTerm)) {
sections.push({
id: t('suggestion.mention.special'),
defaultMessage: 'Special Mentions',
diff --git a/app/components/autocomplete/at_mention/index.js b/app/components/autocomplete/at_mention/index.js
index 33b8ab5cd..011d23cf9 100644
--- a/app/components/autocomplete/at_mention/index.js
+++ b/app/components/autocomplete/at_mention/index.js
@@ -4,6 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
+import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import {autocompleteUsers} from 'mattermost-redux/actions/users';
import {getCurrentChannelId, getDefaultChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
@@ -17,12 +18,26 @@ import {
} from 'app/selectors/autocomplete';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
+import {Permissions} from 'mattermost-redux/constants';
+
import AtMention from './at_mention';
function mapStateToProps(state, ownProps) {
const {cursorPosition, isSearch} = ownProps;
const currentChannelId = getCurrentChannelId(state);
+ let useChannelMentions = true;
+ if (isMinimumServerVersion(state.entities.general.serverVersion, 5, 22)) {
+ useChannelMentions = haveIChannelPermission(
+ state,
+ {
+ channel: currentChannelId,
+ permission: Permissions.USE_CHANNEL_MENTIONS,
+ },
+ );
+ }
+
const value = ownProps.value.substring(0, cursorPosition);
const matchTerm = getMatchTermForAtMention(value, isSearch);
@@ -47,6 +62,7 @@ function mapStateToProps(state, ownProps) {
requestStatus: state.requests.users.autocompleteUsers.status,
theme: getTheme(state),
isLandscape: isLandscape(state),
+ useChannelMentions,
};
}
diff --git a/app/components/post_textbox/index.js b/app/components/post_textbox/index.js
index ac0878ab0..750fbc7e7 100644
--- a/app/components/post_textbox/index.js
+++ b/app/components/post_textbox/index.js
@@ -4,10 +4,12 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
-import {General} from 'mattermost-redux/constants';
+import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
+import {General, Permissions} from 'mattermost-redux/constants';
import {createPost} from 'mattermost-redux/actions/posts';
import {setStatus} from 'mattermost-redux/actions/users';
import {getCurrentChannel, isCurrentChannelReadOnly, getCurrentChannelStats} from 'mattermost-redux/selectors/entities/channels';
+import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
import {canUploadFilesOnMobile, getConfig} from 'mattermost-redux/selectors/entities/general';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
@@ -49,6 +51,17 @@ function mapStateToProps(state, ownProps) {
const currentChannelMembersCount = currentChannelStats?.member_count || 0; // eslint-disable-line camelcase
const isTimezoneEnabled = config?.ExperimentalTimezone === 'true';
+ let useChannelMentions = true;
+ if (isMinimumServerVersion(state.entities.general.serverVersion, 5, 22)) {
+ useChannelMentions = haveIChannelPermission(
+ state,
+ {
+ channel: currentChannel.id,
+ permission: Permissions.USE_CHANNEL_MENTIONS,
+ },
+ );
+ }
+
return {
currentChannel,
channelId: ownProps.channelId || (currentChannel ? currentChannel.id : ''),
@@ -71,6 +84,7 @@ function mapStateToProps(state, ownProps) {
currentChannelMembersCount,
isTimezoneEnabled,
isLandscape: isLandscape(state),
+ useChannelMentions,
};
}
diff --git a/app/components/post_textbox/post_textbox.test.js b/app/components/post_textbox/post_textbox.test.js
index aff9d8601..ba0f878bd 100644
--- a/app/components/post_textbox/post_textbox.test.js
+++ b/app/components/post_textbox/post_textbox.test.js
@@ -64,6 +64,9 @@ describe('PostTextBox', () => {
valueEvent: '',
isLandscape: false,
screenId: 'NavigationScreen1',
+ currentChannelMembersCount: 50,
+ enableConfirmNotificationsToChannel: true,
+ useChannelMentions: true,
};
test('should match, full snapshot', () => {
@@ -117,6 +120,38 @@ describe('PostTextBox', () => {
expect(Alert.alert).toHaveBeenCalledTimes(1);
});
+ test('should send an alert when sending a message with a channel mention', () => {
+ const wrapper = shallowWithIntl(
+ ,
+ );
+ const message = '@all';
+ const instance = wrapper.instance();
+ instance.handleTextChange(message);
+
+ expect(wrapper.state('value')).toBe(message);
+
+ instance.sendMessage();
+ expect(Alert.alert).toBeCalled();
+ expect(Alert.alert).toHaveBeenCalledWith('Confirm sending notifications to entire channel', expect.anything(), expect.anything());
+ });
+
+ test('should not send an alert when sending a message with a channel mention when the user does not have channel mentions permission', () => {
+ const wrapper = shallowWithIntl(
+ ,
+ );
+ const message = '@all';
+ const instance = wrapper.instance();
+ instance.handleTextChange(message);
+
+ expect(wrapper.state('value')).toBe(message);
+
+ instance.sendMessage();
+ expect(Alert.alert).not.toHaveBeenCalled();
+ });
+
test('should return correct @all (same for @channel)', () => {
for (const data of [
{
diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js
index f15971796..4afefd337 100644
--- a/app/components/post_textbox/post_textbox_base.js
+++ b/app/components/post_textbox/post_textbox_base.js
@@ -98,6 +98,7 @@ export default class PostTextBoxBase extends PureComponent {
currentChannel: PropTypes.object,
isLandscape: PropTypes.bool.isRequired,
screenId: PropTypes.string.isRequired,
+ useChannelMentions: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -573,7 +574,7 @@ export default class PostTextBoxBase extends PureComponent {
const {value} = this.state;
const currentMembersCount = this.props.currentChannelMembersCount;
- const notificationsToChannel = this.props.enableConfirmNotificationsToChannel;
+ const notificationsToChannel = this.props.enableConfirmNotificationsToChannel && this.props.useChannelMentions;
const toAllOrChannel = this.textContainsAtAllAtChannel(value);
if (value.indexOf('/') === 0) {